diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index d88522c..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "mintlify.document" - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index adcdf53..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "docwriter.style": "RustDoc" -} \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index db8a7db..b76868a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,47 +1,41 @@ -# Summary - -# Introduction - -# Part 1 - - [Section:1 Getting Started](section-1-getting-started-w/part1) - - [1.1 Basics Concept](section-1-getting-started-w/1.1basics-concept.mc) - - [1.2 Setting up your Enviroment](secting-1-getting-started-w/1.2setting-up-envriment.md) - - [1.3 Entry Point](section-1-getting-started-w/1.3.entry-points.md) - - [1.4 First Steps to write a smart contract](section-1-getting-started-w/1.4.first-steps-write-smartcontract.md) - - - [Section:2 Interacting with Smart Contracts]() - - [1.5 Creating a Query](section-2-interacting-w-smartcontrancts/1.5creating-a-query.md) - - [1.6 Testing a Query](section-2-interacting-w-smartcontracts/1.6testing-a-query.md) - - [1.7 Multi-Test](section-2-interacting-w-smartcontracts/1.7.introducing-multi-test.md) - - - [Section:3 Advanced]() - - [1.8 Contract State](section-3-advanced/1.8.contract-state.md) - - [1.9 Execute a Message](section-3-advanced/1.9executing-a-message.md) - - [1.10 Events,Attributes, and Data](section-3-advanced/1.10event-attribute-data.md) - - [1.11 Dealing with Funds](section-3-advanced/1.11dealing-with-funds.md) - - - [Section:4 Best Practices](section-4-best-practices) - - [1.12 Good Practices](section-4-best-practices/1.12good-practices.md) - - [1.13 Floating Types](section-4-best-practices/1.13floating-point-types.md) - -# Part 2 -- [Part 2](part2) - - [Deep Dive Into the Cosmos](part2/deep-dive-cosmos.md) - - [Interacting with Contracts](part2/interacting-with-contracts.md) - - [Smart Contract Development](part2/smart-contract-development.md) - -# Part 3 -- [Part 3](part3) - - [IBC intergration and Crosschain](part3/ibc-intergration-and-crosschain.md) - - [Optimizing Contracts](part3/optimizing-contract-performance.md) - - [Security and Best Practices](part3/security-and-best-practices.md) - -# Part 4 -- [Part 4](part4) - - [Case Studies](part4/case-studies.md) - - [Ecosystem and Community](part4/ecosystem-and-communication.md) - -# Conclusion -- [Conclusion](conclusion/conclusion.md) - -[Legal Information](impressum.md) \ No newline at end of file +# Summary + +[Introduction](README.md) + +# Getting started + +- [Setting up the environment](setting-up-env.md) +- [Quick start with wasmd](wasmd-quick-start.md) + +# Smart contracts + +- [Basics](basics.md) + - [Create a Rust project](basics/rust-project.md) + - [Entry points](basics/entry-points.md) + - [Building the contract](basics/building-contract.md) + - [Creating a query](basics/query.md) + - [Testing a query](basics/query-testing.md) + - [Introducing multitest](basics/multitest-intro.md) + - [Contract state](basics/state.md) + - [Execution messages](basics/execute.md) + - [Events, attributes and data](basics/events.md) + - [Dealing with funds](basics/funds.md) + - [Good practices](basics/good-practices.md) + - [Floating point types](basics/fp-types.md) + +- [The Actor Model](actor-model.md) + - [The idea](actor-model/idea.md) + - [Actors in the blockchain](actor-model/actors-in-blockchain.md) + - [Contract as an actor](actor-model/contract-as-actor.md) + +- [Cross contract communication](cross-contract.md) + - [Design](cross-contract/design.md) + - [Fixing admin contract](cross-contract/fixing-admin.md) + - [Map storage](cross-contract/map-storage.md) + - [Working with time](cross-contract/working-with-time.md) + +- [Inter-blockchain communication]() + +--- + +[Legal Information](impressum.md) diff --git a/src/actor-model.md b/src/actor-model.md new file mode 100644 index 0000000..ce78331 --- /dev/null +++ b/src/actor-model.md @@ -0,0 +1,9 @@ +# Actor model + +This section describes the fundaments of CosmWasm smart contracts architecture, which determines how do they communicate +with each other. I want to go through this before teaching step by step how to create multiple contracts relating to each +other, to give you a grasp of what to expect. Don't worry if it will not be clear after the first read - I suggest going +through this chapter once now and maybe giving it another take in the future when you know the practical part of this. + +The whole thing described here is officially documented in the +[SEMANTICS.md](https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md), of the `cosmwasm` repository. diff --git a/src/actor-model/actors-in-blockchain.md b/src/actor-model/actors-in-blockchain.md new file mode 100644 index 0000000..178aebd --- /dev/null +++ b/src/actor-model/actors-in-blockchain.md @@ -0,0 +1,326 @@ +# Actors in blockchain + +Previously we were talking about actors mostly in the abstraction of any +blockchain-specific terms. However, before we would dive into the code, we need +to establish some common language, and to do so we would look at contracts from +the perspective of external users, instead of their implementation. + +In this part, I would use the `wasmd` binary to communicate with the malaga +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. + +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! + +So here is my (as a contract developer) point of view on contracts: it is a distributed +database, with some magical mechanisms to make it democratic. Those "magical +mechanisms" are crucial for BC's existence and they make they are reasons why even +use blockchain, but they are not relevant from the contract creator's point of +view - for us, everything that matters is the state. + +But you can say: what about the financial part?! Isn't blockchain (`wasmd` in particular) +the currency implementation? With all of those gas costs, sending funds seems +very much like a money transfer, not database updates. And yes, you are kind of right, +but I have a solution for that too. Just imagine, that for every native token (by +"native tokens" we meant tokens handled directly by blockchain, in contradiction +to for example cw20 tokens) there is a special database bucket (or table if you prefer) +with mapping of address to how much of a token the address possesses. You can query +this table (querying for token balance), but you cannot modify it directly. To modify +it you just send a message to a special build-in bank contract. And everything +is still a database. + +But if blockchain is a database, then where are smart contracts stored? +Obviously - in the database itself! So now imagine another special table - this +one would contain a single table of code-ids mapped to blobs of wasm binaries. And +again - to operate on this table, you use "special contract" which is not accessible +from another contract, but you can use it via `wasmd` binary. + +Now there is a question - why do I even care about BC being a DB? So the reason +is that it makes reasoning about everything in blockchain very natural. Do you +remember that every message in the actor model is transactional? It perfectly +matches traditional database transactions (meaning: every message starts a new +transaction)! Also, when we later talk about migrations, it would turn out, that +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 +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. + +## Compile the contract + +I will not go into the code for now, but to start with something we need compiled +contract binary. The `cw4-group` contract from +[cw-plus](https://github.com/CosmWasm/cw-plus) is simple enough to work with, for +now, so we will start with compiling it. Start with cloning the repository: + +```bash +$ git clone git@github.com:CosmWasm/cw-plus.git +``` + +Then go to `cw4-group` contract and build it: + +```bash +$ cd cw-plus/contracts/cw4-group +$ 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 +``` + +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): + +```bash +$ wasmd tx wasm store ./cw4-group.wasm --from wallet $TXFLAG -y -b block +``` + +As a result of such an operation you would get json output like this: + +``` +.. +logs: +.. +- events: + .. + - attributes: + - key: code_id + value: "1069" + type: store_code +``` + +I ignored most of not fields as they are not relevant for now - what we care +about is the event emitted by blockchain with information about `code_id` of +stored contract - in my case the contract code was stored in blockchain under +the id of `1069`. I can now look at the code by querying for it: + +```bash +$ wasmd query wasm code 1069 code.wasm +``` + +And now the important thing - the contract code is not an actor. So, what is a +contract code? I think that the easiest way to think about that is a `class` or +a `type` in programming. It defines some stuff about what can be done, but the +class itself is in most cases not very useful unless we create an instance +of a type, on which we can call class methods. So now let's move forward to +instances of such contract classes. + +## Contract instance + +Now we have a contract code, but what we want is an actual contract itself. +To create it, we need to instantiate it. Relating to analogy to programming, +instantiation is calling a constructor. To do that, I would send an +instantiate message to my contract: + +```bash +$ wasmd tx wasm instantiate 1069 '{"members": []}' --from wallet --label "Group 1" --no-admin $TXFLAG -y +``` + +What I do here is create a new contract and immediately call the `Instantiate` +message on it. The structure of such a message is different for every contract +code. In particular, the `cw4-group` Instantiate message contains two fields: + +* `members` field which is the list of initial group members optional `admin` +* field which defines an address of who can add or remove + a group member + +In this case, I created an empty group with no admin - so which could never +change! It may seem like a not very useful contract, but it serves us as a +contract example. + +As the result of instantiating, I got the result: + +``` +.. +logs: +.. +- events: + .. + - attributes: + - key: _contract_address + value: wasm1u0grxl65reu6spujnf20ngcpz3jvjfsp5rs7lkavud3rhppnyhmqqnkcx6 + - key: code_id + value: "1069" + type: instantiate +``` + +As you can see, we again look at `logs[].events[]` field, looking for +interesting event and extracting information from it - it is the common case. +I will talk about events and their attributes in the future but in general, +it is a way to notify the world that something happened. Do you remember the +KFC example? If a waiter is serving our dish, he would put a tray on the bar, +and she would yell (or put on the screen) the order number - this would be +announcing an event, so you know some summary of operation, so you can go and +do something useful with it. + +So, what use can we do with the contract? We obviously can call it! But first +I want to tell you about addresses. + +## Addresses in CosmWasm + +Address in CosmWasm is a way to refer to entities in the blockchain. There are +two types of addresses: contract addresses, and non-contracts. The difference +is that you can send messages to contract addresses, as there is some smart +contract code associated with them, and non-contracts are just users of the +system. In an actor model, contract addresses represent actors, and +non-contracts represent clients of the system. + +When operating with blockchain using `wasmd`, you also have an address - you +got one when you added the key to `wasmd`: + +```bash +# add wallets for testing +$ wasmd keys add wallet3 +- name: wallet3 + type: local + address: wasm1dk6sq0786m6ayg9kd0ylgugykxe0n6h0ts7d8t + pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Ap5zuScYVRr5Clz7QLzu0CJNTg07+7GdAAh3uwgdig2X"}' + mnemonic: "" +``` + +You can always check your address: + +```bash +$ wasmd keys show wallet +- name: wallet + type: local + address: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx + pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A5bBdhYS/4qouAfLUH9h9+ndRJKvK0co31w4lS4p5cTE"}' + mnemonic: "" +``` + +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. + +## Querying the contract + +So, we have our contract, let's try to do something with it - query would be the +easiest thing to do. Let's do it: + +```bash +$ wasmd query wasm contract-state smart wasm1u0grxl65reu6spujnf20ngcpz3jvjfsp5rs7lkavud3rhppnyhmqqnkcx6 '{ "list_members": {} }' +data: + members: [] +``` + +The `wasm...` string is the contract address, and you have to substitute it with +your contract address. `{ "list_members": {} }` is query message we send to +contract. Typically, CW smart contract queries are in the form of a single JSON +object, with one field: the query name (`list_members` in our case). The value +of this field is another object, being query parameters - if there are any. +`list_members` query handles two parameters: `limit`, and `start_after`, which +are both optional and which support result pagination. However, in our case of +an empty group they don't matter. + +The query result we got is in human-readable text form (if we want to get the +JSON from - for example, to process it further with `jq`, just pass the +`-o json` flag). As you can see response contains one field: `members` which is +an empty array. + +So, can we do anything more with this contract? Not much. But let's try to do +something with a new one! + +## Executions to perform some actions + +The problem with our previous contract is that for the `cw4-group` contract, +the only one who can perform executions on it is an admin, but our contract +doesn't have one. This is not true for every smart contract, but it is the +nature of this one. + +So, let's make a new group contract, but this time we would +make ourselves an admin. First, check our wallet address: + +```bash +$ 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 +.. +logs: +- events: + .. + - attributes: + - key: _contract_address + value: wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u + - key: code_id + value: "1069" + type: instantiate +``` + +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 +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. + +Now let's query our new contract for the member's list: + +```bash +$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "list_members": {} }' +data: + members: [] +``` + +Just like before - no members initially. Now check an admin: + +``` +$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "admin": {} }' +data: + admin: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx +``` + +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 +``` + +The message for modifying the members is `update_members` and it has two +fields: members to remove, and members to add. Members to remove are +just addresses. Members to add have a bit more complex structure: they +are records with two fields: address and weight. Weight is not relevant +for us now, it is just metadata stored with every group member - for +us, it would always be 1. + +Let's query the contract again to check if our message changed anything: + +```bash +$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "list_members": {} }' +data: + members: + - addr: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx + weight: 1 +``` + +As you can see, the contract updated its state. This is basically how +it works - sending messages to contracts causes them to update the state, +and the state can be queried at any time. For now, to keep things simple +we were just interacting with the contract directly by `wasmd`, but as described +before - contracts can communicate with each other. However, to investigate +this we need to understand how to write contracts. Next time we will look +at the contract structure and we will map it part by part to what we have learned +until now. diff --git a/src/actor-model/contract-as-actor.md b/src/actor-model/contract-as-actor.md new file mode 100644 index 0000000..2cb8b97 --- /dev/null +++ b/src/actor-model/contract-as-actor.md @@ -0,0 +1,426 @@ +# Smart contract as an actor + +In previous chapters, we talked about the actor model and how it is implemented +in the blockchain. Now it is time to look closer into the typical contract +structure to understand how different features of the actor model are mapped to +it. + +This will not be a step-by-step guide on contract creation, as it is a topic +for the series itself. It would be going through contract elements roughly to +visualize how to handle architecture in the actor model. + +## The state + +As before we would start with the state. Previously we were working with +the `cw4-group` contract, so let's start by looking at its code. Go to +`cw-plus/contracts/cw4-group/src`. The folder structure should look like +this: + +```bash +  src +├──  contract.rs +├──  error.rs +├──  helpers.rs +├──  lib.rs +├──  msg.rs +└──  state.rs +``` + +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 +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. + +You can see `Item` is used to keep an admin and some other data: `HOOKS`, and +`TOTAL`. `HOOKS` is used by the `cw4-group` to allow subscription to any +changes to a group - a contract can be added as a hook, so when the group +changes, a message is sent to it. The `TOTAL` is just a sum of all members' +weights. + +The `MEMBERS` in the group contract is the `SnapshotMap` - as you can imagine, +it is a `Map`, with some steroids - this particular one, gives us access to the +state of the map at some point in history, accessing it by the blockchain +`height`. `height` is the count of blocks created since the beggining of +blockchain, and it is the most atomic time representation in smart contracts. +There is a way to access the clock time in them, but everything happening in a +single block is considered happening in the same moment. + +Other types of storage objects not used in group contracts are: + +* `IndexedMap` - another map type, that allows accessing values + by a variety of keys +* `IndexedSnapshotMap` - `IndexedMap` and `SnapshotMap` married + +What is very important - every state type in the contract is accessed using +some name. All of those types are not containers, just accessors to the state. +Do you remember that I told you before that blockchain is our database? And +that is correct! All those types are just ORM to this database - when we use +them to get actual data from it, we pass a special `State` object to them, so +they can retrieve items from it. + +You may ask - why all that data for a contract are not auto-fetched by +whatever is running it. That is a good question. The reason is that we want +contracts to be lazy with fetching. Copying data is a very expensive operation, +and for everything happening on it, someone has to pay - it is realized by gas +cost. I told you before, that as a contract developer you don't need to worry +about gas at all, but it was only partially true. You don't need to know +exactly how gas is calculated, but by lowering your gas cost, you would may +execution of your contracts cheaper which is typically a good thing. One good +practice to achieve that is to avoid fetching data you will not use in a +particular call. + +## Messages + +In a blockchain, contracts communicate with each other by some JSON +messages. They are defined in most contracts in the `msg.rs` file. Take +a look at it. + +There are three types on it, let's go through them one by one. +The first one is an `InstantiateMsg`. This is the one, that is sent +on contract instantiation. It typically contains some data which +is needed to properly initialize it. In most cases, it is just a +simple structure. + +Then there are two enums: `ExecuteMsg`, and `QueryMsg`. They are +enums because every single variant of them represents a different +message which can be sent. For example, the `ExecuteMsg::UpdateAdmin` +corresponds to the `update_admin` message we were sending previously. + +Note, that all the messages are attributed with +`#[derive(Serialize, Deserialize)]`, and +`#[serde(rename_all="snake_case")]`. Those attributes come from +the [serde](https://serde.rs/) crate, and they help us with +deserialization of them (and serialization in case of sending +them to other contracts). The second one is not required, +but it allows us to keep a camel-case style in our Rust code, +and yet still have JSONs encoded with a snake-case style more +typical to this format. + +I encourage you to take a closer look at the `serde` documentation, +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. + +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 +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 +them. They are sent to our contract via entry points. There are +three entry points in the `cw4-group` contract: + +```rust +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + // ... +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + // .. +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + // .. +} +``` + +Those functions are called by the CosmWasm virtual machine when +a message is to be handled by contract. You can think about them +as the `main` function of normal programs, except they have a signature +that better describes the blockchain itself. + +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 +exactly what to call. + +So, let's start with the first line. Every entry point is attributed with +`#[cfg_attr(not(feature = "library"), entry_point)]`. It may look a bit +scary, but it is just a conditional equivalent of `#[entry_point]` - +the attribute would be there if and only if the "library" feature is not set. +We do this to be able to use our contracts as dependencies for other +contracts - the final binary can contain only one copy of each entry point, +so we make sure, that only the top-level one is compiled without this +feature. + +The `entry_point` attribute is a macro that generates some boilerplate. +As the binary is run by WASM virtual machine, it doesn't know much about +Rust types - the actual entry point signatures are very inconvenient to +use. To overcome this issue, there is a macro created, which generates +entry points for us, and those entry points are just calling our functions. + +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: + +* `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. +* `Env` object delivers information about the blockchain state at the + moment of execution - its height, the timestamp of execution and information + about the executing contract itself. +* `MessageInfo` object is information about the contract call - it + contains the address which sends the message, and the funds sent with the + message. + +Keep in mind, that the signatures of those functions are fixed (except +the messages type), so you cannot interchange `Deps` with `DepsMut` to +update the contract state in the query call. + +The last portion of entry points is the return type. Every entry point returns +a `Result` type, with any error which can be turned into a string - in case of +contract failure, the returned error is just logged. In most cases, the error +type is defined for a contract itself, typically using a +[thiserror](https://docs.rs/thiserror/latest/thiserror/) crate. `Thiserror` is +not required here, but is strongly recommended - using it makes the error +definition very straightforward, and also improves the testability of the +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 +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 +needed to complete contract execution. There are three chunks of +information in that. + +The first one is an `events` field. It contains all events, which would +be emitted to the blockchain as a result of the execution. Events have +a really simple structure: they have a type, which is just a string, +and a list of attributes which are just string-string key-value pairs. + +You can notice that there is another `attributes` field on the `Response`. +This is just for convenience - most executions would return +only a single event, and to make it a bit easier to operate one, there +is a set of attributes directly on response. All of them would be converted +to a single `wasm` event which would be emitted. Because of that, I consider +`events` and `attributes` to be the same chunk of data. + +Then we have the messages field, of `SubMsg` type. This one is the clue +of cross-contact communication. Those messages would be sent to the +contracts after processing. What is important - the whole execution is +not finished, unless the processing of all sub-messages scheduled by the contract +finishes. So, if the group contract sends some messages as a result of +`update_members` execution, the execution would be considered done only if +all the messages sent by it would also be handled (even if they failed). + +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 +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 +message handling fails). So, if your contract emitted some event as a result of +being sub-call of some bigger use case, the event would always be there visible +to everyone. This is not true for data. Every contract call would return only +a single `data` chunk, and it has to decide if it would just forward the `data` +field of one of the sub-calls, or maybe it would construct something by itself. +I would explain it in a bit more detail in a while. + +## 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 +about sending messages. + +The first function to use here is `add_message`, which takes as an argument the +`CosmosMsg` (or rather anything convertible to it). A message added to response +this way would be sent and processed, and its execution would not affect the +result of the contract at all. + +The other function to use is `add_submessage`, taking a `SubMsg` argument. It +doesn't differ much from `add_message` - `SubMsg` just wraps the `CosmosMsg`, +adding some info to it: the `id` field, and `reply_on`. There is also a +`gas_limit` thing, but it is not so important - it just causes sub-message +processing to fail early if the gas threshold is reached. + +The simple thing is `reply_on` - it describes if the `reply` message should be +sent on processing success, on failure, or both. + +The `id` field is an equivalent of the order id in our KFC example from the +very beginning. If you send multiple different sub-messages, it would be +impossible to distinguish them without that field. It would not even be +possible to figure out what type of original message reply handling is! This is +why the `id` field is there - sending a sub-message you can set it to any +value, and then on the reply, you can figure out what is happening based on +this field. + +An important note here - you don't need to worry about some sophisticated way +of generating ids. Remember, that the whole processing is atomic, and only one +execution can be in progress at once. In most cases, your contract sends a +fixed number of sub-messages on very concrete executions. Because of that, you +can hardcode most of those ids while sending (preferably using some constant). + +To easily create submessages, instead of setting all the fields separately, +you would typically use helper constructors: `SubMsg::reply_on_success`, +`SubMsg::reply_on_error` and `SubMsg::reply_always`. + +## CosmosMsg + +If you took a look at the `CosmosMsg` type, you could be very surprised - there +are so many variants of them, and it is not obvious how they relate to +communication with other contracts. + +The message you are looking for is the `WasmMsg` (`CosmosMsg::Wasm` variant). +This one is very much similar to what we already know - it has a couple of +variants of operation to be performed by contracts: `Execute`, but also +`Instantiate` (so we can create new contracts in contract executions), and also +`Migrate`, `UpdateAdmin`, and `ClearAdmin` - those are used to manage +migrations (will tell a bit about them at the end of this chapter). + +Another interesting message is the `BankMsg` (`CosmosMsg::Bank`). This one +allows a contract to transfer native tokens to other contracts (or burn them - +equivalent to transferring them to some black whole contract). I like to think +about it as sending a message to a very special contract responsible for handling +native tokens - this is not a true contract, as it is handled by the blockchain +itself, but at least to me it simplifies things. + +Other variants of `CosmosMsg` are not very interesting for now. The `Custom` +one is there to allow other CosmWasm-based blockchains to add some +blockchain-handled variant of the message. This is a reason why most +message-related types in CosmWasm are generic over some `T` - this is just a +blockchain-specific type of message. We will never use it in the `wasmd`. All +other messages are related to advanced CosmWasm features, and I will not +describe them here. + +## Reply handling + +So now that we know how to send a submessage, it is time to talk about +handling the reply. When sub-message processing is finished, and it is +requested to reply, the contract is called with an entry point: + +```rust +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + // ... +} +``` + +The `DepsMut`, and `Env` arguments are already familiar, but there is a new +one, substituting the typical message argument: the `cosmwasm_std::Reply`. + +This is a type representing the execution status of the sub-message. It is +slightly processed `cosmwasm_std::Response`. The first important thing it contains +is an `id` - the same, which you set sending sub-message, so now you can +identify your response. The other one is the `ContractResult`, which is very +similar to the Rust `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 +before, errors are converted to strings right after execution. The `Ok` case +contains `SubMsgExecutionResponse` with two fields: `events` emitted by +sub-call, and the `data` field embedded on response. + +As said before, you never need to worry about forwarding events - CosmWasm +would do it anyway. The `data` however, is another story. As mentioned before, +every call would return only a single data object. In the case of sending +sub-messages and not capturing a reply, it would always be whatever is returned +by the top-level message. But it is not the case when `reply` is called. If a +a reply is called, then it is a function deciding about the final `data`. It can +decide to either forward the data from the sub-message (by returning `None`) or +to overwrite it. It cannot choose, to return data from the original execution +processing - if the contract sends sub-messages waiting for replies, it is +supposed to not return any data, unless replies are called. + +But what happens if multiple sub-messages are sent? What would the final +`data` contain? The rule is - the last non-None. All sub-messages are always +called in the order of adding them to the `Response`. As the order is +deterministic and well defined, it is always easy to predict which reply would +be used. + +## Migrations + +I mentioned migrations earlier when describing the `WasmMsg`. So, migration +is another action possible to be performed by contracts, which is kind +of similar to instantiate. In software engineering, it is a common thing to +release an updated version of applications. It is also a case in the blockchain - +SmartContract can be updated with some new features. In such cases, a new +code is uploaded, and the contract is migrated - so it knows that from +this point, its messages are handled by another, updated contract code. + +However, it may be that the contract state used by the older version of the +contract differs from the new one. It is not a problem if some info was +added (for example some additional map - it would be just empty right +after migration). But the problem is, when the state changes, +for example, the field is renamed. In such a case, every contract execution +would fail because of (de)serialization problems. Or even more subtle +cases, like adding a map, but one which should be synchronized with the whole +contract state, not empty. + +This is the purpose of the `migration` entry point. It looks like this: + +```rust +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result, ContracError> { + // .. +} +``` + +`MigrateMsg` is the type defined by the contract in `msg.rs`. +The `migrate` entry point would be called at the moment of performing +the migration, and it is responsible for making sure the state is correct +after the migration. It is very similar to schema migrations in traditional +database applications. And it is also kind of difficult, because of version +management involved - you can never assume, that you are migrating a contract +from the previous version - it can be migrated from any version, released +anytime - even later than that version we are migrating to! + +It is worth bringing back one issue from the past - the contract admin. Do you +remember the `--no-admin` flag we set previously on every contract +instantiation? It made our contract unmigrateable. Migrations can be performed +only by contract admin. To be able to use it, you should pass `--admin address` +flag instead, with the `address` being the address that would be able to +perform migrations. + +## Sudo + +Sudo is the last basic entry point in `CosmWasm`, and it is the one we would +never use in `wasmd`. It is equivalent to `CosmosMsg::Custom`, but instead of +being a special blockchain-specific message to be sent and handled by a +blockchain itself, it is now a special blockchain-specific message sent by the +blockchain to contract in some conditions. There are many uses for those, but I +will not cover them, because would not be related to `CosmWasm` itself. The +signature of `sudo` looks like this: + +```rust +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { + // .. +} +``` + +The important difference is that because `sudo` messages are blockchain +specific, the `SudoMsg` type is typically defined by some blockchain helper +crate, not the contract itself. diff --git a/src/actor-model/idea.md b/src/actor-model/idea.md new file mode 100644 index 0000000..3a5dd65 --- /dev/null +++ b/src/actor-model/idea.md @@ -0,0 +1,319 @@ +# Idea behind an Actor Model + +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. + +## The problem + +Smart contracts can be imagined as sandboxed microservices. Due to +[SOLID](https://en.wikipedia.org/wiki/SOLID) principles, it is valuable to +split responsibilities between entities. However, to split the work between +contracts themselves, there is a need to communicate between them, so if one +contract is responsible for managing group membership, it is possible to call +its functionality from another contract. + +The traditional way to solve this problem in SW engineering is to model +services as functions that would be called with some RPC mechanism, and return +its result as a response. Even though this approach looks nice, it creates sort +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 +finished, it will call the original contract back. + +This solution may feel unnatural, and it requires different kinds of design +solutions, but it turns out to work pretty well for smart contract execution. +I will try to explain how to reason about it, and how it maps to contract +structure step by step. + +## The Actor + +The most important thing in the whole model is an Actor itself. So, what is +this? The Actor is a single instantiation of a contract, which can perform +several actions. When the actor finishes his job, he prepares a summary of it, +which includes the list of things that have to be done, to complete the whole +scheduled task. + +An example of an actor is the Seller in the KFC restaurant. The first thing you +do is order your BSmart, so you are requesting action from him. So, from the +system user, you can think about this task as "sell and prepare my meal", but +the action performed by the seller is just "Charge payment and create order". +The first part of this operation is to create a bill and charge you for it, and +then it requests the Sandwich and Fries to be prepared by other actors, +probably chefs. Then when the chef is done with his part of the meal, he checks +if all meals are ready. If so, it calls the last actor, the waiter, to deliver +the food to you. At this point, you can receive your delivery, and the task is +considered complete. + +The above-described workflow is kind of simplified. In particular - in a +typical restaurant, a waiter would observe the kitchen instead of being +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 - +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. + +## The Action + +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 +at performing both "Prepare fries" and "Prepare Sandwich" actions - and also +many more. + +So, when we want to do something in an actor system, we schedule some action to +the actor being the closest to us, very often with some additional parameters +(as we can pick if we want to exchange fries with salad). + +However, naming the action after the exact thing which happened in the very +contract would be misleading. Take a look at the KFC example once again. As I +mentioned, the action performed by a seller is "Charge payment and create +order". The problem is, that for the client who schedules this action, it +doesn't matter what exactly is the responsibility of the actor himself - what +the client is scheduling is "Prepare Meal" with some description of what +exactly to prepare. So, we can say, that the action is the thing performed by +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 +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? + +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. + +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 +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 +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 +for a client), or the seller is just hungry. It is possible that not only the +seller is eligible to call the chef for food - possibly any restaurant employee +can do that just for themselves. Therefore, we need a way to be able to react +to an actor finishing his job in some universal way, to handle this situation +properly in any context. + +It is worth noting that the `Reply` can contain some additional data. The id +assigned previously is the only required information in the `Reply` call, but +the actor can pass some additional data - `events` emitted, which are mostly +metadata (to be observed by non-blockchain applications mostly), and any +arbitrary data it wants to pass. + +## State + +Up until this point, we were considering actors as entities performing some +job, like preparing the meal. If we are considering computer programs, such a +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 - +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 +real-life Smart Contract than the restaurant. Let's imagine we want to create +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 +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 +[`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). + +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 +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 +hidden, it has to be stored indirectly. + +There is one very important thing about the state in CosmWasm, and it is the +state being transactional. Any updates to the state are not applied +immediately, but only when the whole action succeeds. It is very important, as +it guarantees that if something goes wrong in the contract, it is always left +in some proper state. Let's consider our `MmoCurrency` case. Imagine, that in +the `Transfer` action we first increase the receiver currency amount (by +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 +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 +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 +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 +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 +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 +manner. + +However, the existence of queries doesn't mean that we cannot look at the +contract's state directly - the state is still public, and the technique of +looking at them directly is called `Raw Queries`. For clarity, non-raw queries +are sometimes denoted as `Smart Queries`. + +## Wrapping everything together - transactional call flow + +So, we touched on many things here, and I know it may be kind of confusing. +Because of that, I would like to go through some more complicated calls to the +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 + `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 + 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 +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 +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. + +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 +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 +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 +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 +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 + 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 +out (as in transfer failure, the list is not updated). We can do better. + +## Different ways of handling responses + +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: + +1. Never call `Reply`, action fails if sub-message fails +2. Call `Reply` on success +3. Call `Reply` on failure +4. Always call `Reply` + +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. + +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 +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 +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 +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 +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 +created contract. + +In the end, you can see that performing actions in CosmWasm is built with +hierarchical state change transactions. The sub-transaction can be applied to +the blockchain only if everything succeeds, but in case that sub-transaction +failed, only its part may be rolled back, end other changes may be applied. It +is very similar to how most database systems work. + +## Conclusion + +Now you have seen the power of the actor model to avoid reentrancy, properly +handle errors, and safely sandbox contracts. This helps us provide the solid +security guarantees of the CosmWasm platform. Let’s get started playing around +with real contracts in the `wasmd` blockchain. diff --git a/src/basics.md b/src/basics.md new file mode 100644 index 0000000..46e719a --- /dev/null +++ b/src/basics.md @@ -0,0 +1,4 @@ +# Basics + +In this chapter, we will go through creating basic smart contracts step by step. +I will try to explain the core ideas behind CosmWasm and the typical contract structure. diff --git a/src/basics/building-contract.md b/src/basics/building-contract.md new file mode 100644 index 0000000..8db6a67 --- /dev/null +++ b/src/basics/building-contract.md @@ -0,0 +1,57 @@ +# Building the contract + +Now it is time to build our contract. We can use a traditional cargo build +pipeline for local testing purposes: `cargo build` for compiling it and `cargo +test` for running all tests (which we don't have yet, but we will work on that +soon). + +However, we need to create a wasm binary to upload the contract to blockchain. +We can do it by passing an additional argument to the build command: + +``` +$ cargo build --target wasm32-unknown-unknown --release +``` + +The `--target` argument tells cargo to perform cross-compilation for a given target instead of +building a native binary for an OS it is running on - in this case, `wasm32-unknown-unknown`, +which is a fancy name for Wasm target. + +Additionally, I passed the `--release` argument to the command - it is not +required, but in most cases, debug information is not very useful while running +on-chain. It is crucial to reduce the uploaded binary size for gas cost +minimization. It is worth knowing that there is a [CosmWasm Rust +Optimizer](https://github.com/CosmWasm/rust-optimizer) tool that takes care of +building even smaller binaries. For production, all the contracts should be +compiled using this tool, but for learning purposes, it is not an essential +thing to do. + +## Aliasing build command + +Now I can see you are disappointed in building your contracts with some overcomplicated command +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: + +```toml +[alias] +wasm = "build --target wasm32-unknown-unknown --release" +wasm-debug = "build --target wasm32-unknown-unknown" +``` + +Now, building your Wasm binary is as easy as executing `cargo wasm`. We also added the additional +`wasm-debug` command for rare cases when we want to build the wasm binary, including debug information. + +## Checking contract validity + +When the contract is built, the last step is to ensure it is a valid CosmWasm contract is to call +`cosmwasm-check` on it: + +``` +$ cargo wasm +... +$ cosmwasm-check ./target/wasm32-unknown-unknown/release/contract.wasm +Available capabilities: {"cosmwasm_1_1", "staking", "stargate", "iterator", "cosmwasm_1_2"} + +./target/wasm32-unknown-unknown/release/contract.wasm: pass +``` diff --git a/src/basics/entry-points.md b/src/basics/entry-points.md new file mode 100644 index 0000000..39ef7c3 --- /dev/null +++ b/src/basics/entry-points.md @@ -0,0 +1,78 @@ +# Entry points + +Typical Rust application starts with the `fn main()` function called by the operating system. +Smart contracts are not significantly different. When the message is sent to the contract, a +function called "entry point" is called. Unlike native applications, which have only a single +`main` entry point, smart contracts have a couple corresponding to different message types: +`instantiate`, `execute`, `query`, `sudo`, `migrate` and more. + +To start, we will go with three basic entry points: + +* `instantiate` which is called once per smart contract lifetime - you can think about it as + a constructor or initializer of a contract. +* `execute` for handling messages which are able to modify contract state - they are used to + perform some actual actions. +* `query` for handling messages requesting some information from a contract; unlike `execute`, + they can never affect any contract state, and are used just like database queries. + +Go to your `src/lib.rs` file, and start with an `instantiate` entry point: + +```rust,noplayground +use cosmwasm_std::{ + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; + +#[entry_point] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::new()) +} +``` + +In fact, `instantiate` is the only entry point required for a smart contract to be valid. It is not +very useful in this form, but it is a start. Let's take a closer look at the entry point structure. + +First, we start with importing couple of types just for more consistent usage. Then we define our +entry point. The `instantiate` takes four arguments: + +* [`deps: DepsMut`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.DepsMut.html) + is a utility type for communicating with the outer world - it allows querying + and updating the contract state, querying other contracts state, and gives access to an `Api` + object with a couple of helper functions for dealing with CW addresses. +* [`env: Env`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Env.html) + is an object representing the blockchains state when executing the message - the + chain height and id, current timestamp, and the called contract address. +* [`info: MessageInfo`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.MessageInfo.html) + contains metainformation about the message which triggered an execution - + an address that sends the message, and chain native tokens sent with the message. +* [`msg: Empty`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Empty.html) + is the message triggering execution itself - for now, it is `Empty` type that + represents `{}` JSON, but the type of this argument can be anything that is deserializable, + and we will pass more complex types here in the future. + +If you are new to the blockchain, those arguments may not have much sense to you, but while +progressing with this guide, I will explain their usage one by one. + +Notice an essential attribute decorating our entry point +[`#[entry_point]`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/attr.entry_point.html). Its purpose is to +wrap the whole entry point to the form Wasm runtime understands. The proper Wasm entry points +can use only basic types supported natively by Wasm specification, and Rust structures and enums +are not in this set. Working with such entry points would be rather overcomplicated, so CosmWasm +creators delivered the `entry_point` macro. It creates the raw Wasm entry point, calling the +decorated function internally and doing all the magic required to build our high-level Rust arguments +from arguments passed by Wasm runtime. + +The next thing to look at is the return type. I used +[`StdResult`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/type.StdResult.html) for this simple example, +which is an alias for `Result`. The return entry point type would always be a +[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) type, with some error type implementing +[`ToString`](https://doc.rust-lang.org/std/string/trait.ToString.html) trait and a well-defined type for success +case. For most entry points, an "Ok" case would be the +[`Response`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html) type that allows fitting the contract +into our actor model, which we will discuss very soon. + +The body of the entry point is as simple as it could be - it always succeeds with a trivial empty response. diff --git a/src/basics/events.md b/src/basics/events.md new file mode 100644 index 0000000..9fef0be --- /dev/null +++ b/src/basics/events.md @@ -0,0 +1,603 @@ +# Events attributes and data + +The only way our contract can communicate to the world, for now, is by queries. +Smart contracts are passive - they cannot invoke any action by themselves. They +can do it only as a reaction to a call. But if you tried playing with `wasmd`, +you know that execution on the blockchain can return some metadata. + +There are two things the contract can return to the caller: events and data. +Events are something produced by almost every real-life smart contract. In +contrast, data is rarely used, designed for contract-to-contract communication. + +## Returning events + +As an example, we would add an event `admin_added` emitted by our contract on the execution of +`AddMembers`: + +```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, +}; + +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# pub fn execute( +# deps: DepsMut, +# _env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# use ExecuteMsg::*; +# +# match msg { +# AddMembers { admins } => exec::add_members(deps, info, admins), +# Leave {} => exec::leave(deps, info).map_err(Into::into), +# } +# } +# +mod exec { +# use super::*; +# + pub fn add_members( + deps: DepsMut, + info: MessageInfo, + admins: Vec, + ) -> Result { + let mut curr_admins = ADMINS.load(deps.storage)?; + if !curr_admins.contains(&info.sender) { + 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()); + + let admins: StdResult> = admins + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect(); + + curr_admins.append(&mut admins?); + ADMINS.save(deps.storage, &curr_admins)?; + + Ok(resp) + } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +} +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# +# #[test] +# fn unauthorized() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let err = app +# .execute_contract( +# Addr::unchecked("user"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap_err(); +# +# assert_eq!( +# ContractError::Unauthorized { +# sender: Addr::unchecked("user") +# }, +# err.downcast().unwrap() +# ); +# } +# } +``` + +An event is built from two things: an event type provided in the +[`new`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Event.html#method.new) function and attributes. +Attributes are added to an event with +the [`add_attributes`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Event.html#method.add_attributes) +or the [`add_attribute`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Event.html#method.add_attribute) +call. Attributes are key-value pairs. Because an event cannot contain any list, to achieve reporting +multiple similar actions taking place, we need to emit multiple small events instead of a collective one. + +Events are emitted by adding them to the response with +[`add_event`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.add_event) or +[`add_events`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.add_events) call. +Additionally, there is a possibility to add attributes directly to the response. It is just sugar. By default, +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: + +```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, +# }; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# pub fn execute( +# deps: DepsMut, +# _env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# use ExecuteMsg::*; +# +# match msg { +# AddMembers { admins } => exec::add_members(deps, info, admins), +# Leave {} => exec::leave(deps, info).map_err(Into::into), +# } +# } +# +# mod exec { +# use super::*; +# +# pub fn add_members( +# deps: DepsMut, +# info: MessageInfo, +# admins: Vec, +# ) -> Result { +# let mut curr_admins = ADMINS.load(deps.storage)?; +# if !curr_admins.contains(&info.sender) { +# 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()); +# +# let admins: StdResult> = admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# +# curr_admins.append(&mut admins?); +# ADMINS.save(deps.storage, &curr_admins)?; +# +# Ok(resp) +# } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# +# #[test] +# fn unauthorized() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let err = app +# .execute_contract( +# Addr::unchecked("user"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap_err(); +# +# assert_eq!( +# ContractError::Unauthorized { +# sender: Addr::unchecked("user") +# }, +# err.downcast().unwrap() +# ); +# } +# + #[test] + fn add_members() { + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { + admins: vec!["owner".to_owned()], + }, + &[], + "Contract", + None, + ) + .unwrap(); + + let resp = app + .execute_contract( + Addr::unchecked("owner"), + addr, + &ExecuteMsg::AddMembers { + admins: vec!["user".to_owned()], + }, + &[], + ) + .unwrap(); + + let wasm = resp.events.iter().find(|ev| ev.ty == "wasm").unwrap(); + assert_eq!( + wasm.attributes + .iter() + .find(|attr| attr.key == "action") + .unwrap() + .value, + "add_members" + ); + assert_eq!( + wasm.attributes + .iter() + .find(|attr| attr.key == "added_count") + .unwrap() + .value, + "1" + ); + + let admin_added: Vec<_> = resp + .events + .iter() + .filter(|ev| ev.ty == "wasm-admin_added") + .collect(); + assert_eq!(admin_added.len(), 1); + + assert_eq!( + admin_added[0] + .attributes + .iter() + .find(|attr| attr.key == "addr") + .unwrap() + .value, + "user" + ); + } +} +``` + +As you can see, testing events on a simple test made it clunky. First of all, +every string is heavily string-based - a lack of type control makes writing +such tests difficult. Also, even types are prefixed with "wasm-" - it may not +be a huge problem, but it doesn't clarify verification. But the problem is, how +layered events structure are, which makes verifying them tricky. Also, the +"wasm" event is particularly tricky, as it contains an implied attribute - +`_contract_addr` containing an address called a contract. My general rule is - +do not test emitted events unless some logic depends on them. + +## Data + +Besides events, any smart contract execution may produce a `data` object. In contrast to events, `data` +can be structured. It makes it a way better choice to perform any communication logic relies on. On the +other hand, it turns out it is very rarely helpful outside of contract-to-contract communication. Data +is always only one single object on the response, which is set using the +[`set_data`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Response.html#method.set_data) function. +Because of its low usefulness in a single contract environment, we will not spend time on it right now - an +example of it will be covered later when contract-to-contract communication will be discussed. Until then, +it is just helpful to know such an entity exists. diff --git a/src/basics/execute.md b/src/basics/execute.md new file mode 100644 index 0000000..e94a15e --- /dev/null +++ b/src/basics/execute.md @@ -0,0 +1,880 @@ +# Execution messages + +We went through instantiate and query messages. It is finally time to introduce the last basic entry point - +the execute messages. It is similar to what we have done so far that I expect this to be just chilling and +revisiting our knowledge. I encourage you to try implementing what I am describing here on your own as an +exercise - without checking out the source code. + +The idea of the contract will be easy - every contract admin would be eligible to call two execute messages: + +* `AddMembers` message would allow the admin to add another address to the admin's list +* `Leave` would allow and admin to remove himself from the list + +Not too complicated. Let's go coding. Start with defining messages: + +```rust,noplayground +# use cosmwasm_std::Addr; +# use serde::{Deserialize, Serialize}; +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct InstantiateMsg { +# pub admins: Vec, +# } +# +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum ExecuteMsg { + AddMembers { admins: Vec }, + Leave {}, +} +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct GreetResp { +# pub message: String, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct AdminsListResp { +# pub admins: Vec, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub enum QueryMsg { +# Greet {}, +# AdminsList {}, +# } +``` + +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}; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } + +#[allow(dead_code)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + use ExecuteMsg::*; + + match msg { + AddMembers { admins } => exec::add_members(deps, info, admins), + Leave {} => exec::leave(deps, info), + } +} + +mod exec { + use cosmwasm_std::StdError; + + use super::*; + + pub fn add_members( + deps: DepsMut, + info: MessageInfo, + admins: Vec, + ) -> StdResult { + let mut curr_admins = ADMINS.load(deps.storage)?; + if !curr_admins.contains(&info.sender) { + return Err(StdError::generic_err("Unauthorised access")); + } + + let admins: StdResult> = admins + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect(); + + curr_admins.append(&mut admins?); + ADMINS.save(deps.storage, &curr_admins)?; + + Ok(Response::new()) + } + + pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { + ADMINS.update(deps.storage, move |admins| -> StdResult<_> { + let admins = admins + .into_iter() + .filter(|admin| *admin != info.sender) + .collect(); + Ok(admins) + })?; + + Ok(Response::new()) + } +} +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# } +``` + +The entry point itself also has to be created in `src/lib.rs`: + +```rust,noplayground +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +mod contract; +mod msg; +mod state; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + contract::instantiate(deps, env, info, msg) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + contract::execute(deps, env, info, msg) +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + contract::query(deps, env, msg) +} +``` + +There are a couple of new things, but nothing significant. First is how do I reach the message sender +to verify he is an admin or remove him from the list - I used the `info.sender` field of `MessageInfo`, +which is how it looks like - the member. As the message is always sent from the proper address, the +`sender` is already of the `Addr` type - no need to validate it. Another new thing is the +[`update`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.update) +function on an `Item` - it makes a read and update of an entity potentially more efficient. It is +possible to do it by reading admins first, then updating and storing the result. + +You probably noticed that when working with `Item`, we always assume something +is there. But nothing forces us to initialize the `ADMINS` value on +instantiation! So what happens there? Well, both `load` and `update` functions +would return an error. But there is a +[`may_load`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.may_load) +function, which returns `StdResult>` - it would return `Ok(None)` in +case of empty storage. There is even a possibility to remove an existing item +from storage with the +[`remove`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.remove) +function. + +One thing to improve is error handling. While validating the sender to be admin, we are returning +some arbitrary string as an error. We can do better. + +## Error handling + +In our contract, we now have an error situation when a user tries to execute `AddMembers` not being +an admin himself. There is no proper error case in +[`StdError`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/enum.StdError.html) to report this +situation, so we have to return a generic error with a message. It is not the best approach. + +For error reporting, we encourage using [`thiserror`](https://crates.io/crates/thiserror/1.0.24/dependencies) +crate. Start with updating your dependencies: + +```toml +[package] +name = "contract" +version = "0.1.0" +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" + +[dev-dependencies] +cw-multi-test = "0.13.4" +``` + +Now we define an error type in `src/error.rs`: + +```rust,noplayground +use cosmwasm_std::{Addr, StdError}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + StdError(#[from] StdError), + #[error("{sender} is not contract admin")] + Unauthorized { sender: Addr }, +} +``` + +We also need to add the new module to `src/lib.rs`: + +```rust,noplayground +# use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +# +mod contract; +mod error; +mod msg; +mod state; +# +# #[entry_point] +# pub fn instantiate( +# deps: DepsMut, +# env: Env, +# info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# contract::instantiate(deps, env, info, msg) +# } +# +# #[entry_point] +# pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { +# contract::execute(deps, env, info, msg) +# } +# +# #[entry_point] +# pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +# contract::query(deps, env, msg) +# } +``` + +Using `thiserror` we define errors like a simple enum, and the crate ensures +that the type implements +[`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) +trait. A very nice feature of this crate is the inline definition of +[`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait by an +`#[error]` attribute. Also, another helpful thing is the `#[from]` attribute, +which automatically generates proper +[`From`](https://doc.rust-lang.org/std/convert/trait.From.html) implementation, +so it is easy to use `?` operator with `thiserror` types. + +Now update the execute endpoint to use our new error type: + +```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, MessageInfo, Response, StdResult}; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } + +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + use ExecuteMsg::*; + + match msg { + AddMembers { admins } => exec::add_members(deps, info, admins), + Leave {} => exec::leave(deps, info).map_err(Into::into), + } +} + +mod exec { + use super::*; + + pub fn add_members( + deps: DepsMut, + info: MessageInfo, + admins: Vec, + ) -> Result { + let mut curr_admins = ADMINS.load(deps.storage)?; + if !curr_admins.contains(&info.sender) { + return Err(ContractError::Unauthorized { + sender: info.sender, + }); + } + + let admins: StdResult> = admins + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect(); + + curr_admins.append(&mut admins?); + ADMINS.save(deps.storage, &curr_admins)?; + + Ok(Response::new()) + } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +} +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# } +``` + +The entry point return type also has to be updated: + +```rust,noplayground +# use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use error::ContractError; +# use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +# mod contract; +# mod error; +# mod msg; +# mod state; +# +# #[entry_point] +# pub fn instantiate( +# deps: DepsMut, +# env: Env, +# info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# contract::instantiate(deps, env, info, msg) +# } +# +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + contract::execute(deps, env, info, msg) +} +# +# #[entry_point] +# pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +# contract::query(deps, env, msg) +# } +``` + +## Custom error and multi-test + +Using proper custom error type has one nice upside - multi-test is maintaining error type using +the [`anyhow`](https://crates.io/crates/anyhow) crate. It is a sibling of `thiserror`, designed +to implement type-erased errors in a way that allows getting the original error back. + +Let's write a test that verifies that a non-admin cannot add himself to a list: + +```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, MessageInfo, Response, StdResult}; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# pub fn execute( +# deps: DepsMut, +# _env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# use ExecuteMsg::*; +# +# match msg { +# AddMembers { admins } => exec::add_members(deps, info, admins), +# Leave {} => exec::leave(deps, info).map_err(Into::into), +# } +# } +# +# mod exec { +# use super::*; +# +# pub fn add_members( +# deps: DepsMut, +# info: MessageInfo, +# admins: Vec, +# ) -> Result { +# let mut curr_admins = ADMINS.load(deps.storage)?; +# if !curr_admins.contains(&info.sender) { +# return Err(ContractError::Unauthorized { +# sender: info.sender, +# }); +# } +# +# let admins: StdResult> = admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# +# curr_admins.append(&mut admins?); +# ADMINS.save(deps.storage, &curr_admins)?; +# +# Ok(Response::new()) +# } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# + #[test] + fn unauthorized() { + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { admins: vec![] }, + &[], + "Contract", + None, + ) + .unwrap(); + + let err = app + .execute_contract( + Addr::unchecked("user"), + addr, + &ExecuteMsg::AddMembers { + admins: vec!["user".to_owned()], + }, + &[], + ) + .unwrap_err(); + + assert_eq!( + ContractError::Unauthorized { + sender: Addr::unchecked("user") + }, + err.downcast().unwrap() + ); + } +} +``` + +Executing a contract is very similar to any other call - we use an +[`execute_contract`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/trait.Executor.html#method.execute_contract) +function. As the execution may fail, we get an error type out of this call, but +instead of calling `unwrap` to extract a value out of it, we expect an error to +occur - this is the purpose of the +[`unwrap_err`](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err) +call. Now, as we have an error value, we can check if it matches what we +expected with an `assert_eq!`. There is a slight complication - the error +returned from `execute_contract` is an +[`anyhow::Error`](https://docs.rs/anyhow/1.0.57/anyhow/struct.Error.html) +error, but we expect it to be a `ContractError`. Fortunately, as I said before, +`anyhow` errors can recover their original type using the +[`downcast`](https://docs.rs/anyhow/1.0.57/anyhow/struct.Error.html#method.downcast) +function. The `unwrap` right after it is needed because downcasting may fail. +The reason is that `downcast` doesn't magically know the type kept in the +underlying error. It deduces it by some context - here, it knows we expect it +to be a `ContractError`, because of being compared to it - type elision +miracles. But if the underlying error would not be a `ContractError`, then +`unwrap` would panic. + +We just created a simple failure test for execution, but it is not enough to claim the contract is production-ready. +All reasonable ok-cases should be covered for that. I encourage you to create some tests and experiment with them as +an exercise after this chapter. diff --git a/src/basics/fp-types.md b/src/basics/fp-types.md new file mode 100644 index 0000000..1ec1bbc --- /dev/null +++ b/src/basics/fp-types.md @@ -0,0 +1,24 @@ +# Floating point types + +Now you are ready to create smart contracts on your own. It is time to discuss an important limitation of CosmWasm +smart contracts - floating-point numbers. + +The story is short: you cannot use floating-point types in smart contracts. Never. CosmWasm virtual machine on purpose +does not implement floating-point Wasm instructions, even such basics as `F32Load`. The reasoning is simple: they are +not safe to work with in the blockchain world. + +The 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). + +This limitation has two implications. First, you always have to use decimal of fixed-point arithmetic in your contracts. +It is not a problem, considering that `cosmwasm-std` provides you with the +[`Decimal`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Decimal.html) and +[Decimal256](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Decimal256.html) types. + +The other implication is tricky - you must be careful with the crates you use. In particular, one gotcha in the `serde` +crate - deserialization of `usize` type is using floating-point operations. That means you can never use `usize` (or `isize`) +types in your deserialized messages in the contract. + +Another thing that will not work with serde is untagged enums deserialization. The workaround is to create custom +deserialization of such enums using [`serde-cw-value`](https://crates.io/crates/serde-cw-value) crate. It is a fork of +[`serde-value`](https://crates.io/crates/serde-value) crate which avoids generating floating-point instructions. + diff --git a/src/basics/funds.md b/src/basics/funds.md new file mode 100644 index 0000000..223d42e --- /dev/null +++ b/src/basics/funds.md @@ -0,0 +1,1257 @@ +# Dealing with funds + +When you hear smart contracts, you think blockchain. When you hear blockchain, +you often think of cryptocurrencies. It is not the same, but crypto assets, or +as we often call them: tokens, are very closely connected to the blockchain. +CosmWasm has a notion of a native token. Native tokens are assets managed by +the blockchain core instead of smart contracts. Often such assets have some +special meaning, like being used for paying [gas +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. + +## Preparing messages + +Traditionally we need to prepare our messages. We need to create a new +`ExecuteMsg` variant, but we will also modify the `Instantiate` message a bit - +we need to have some way of defining the name of a native token we would use +for donations. It would be possible to allow users to send any tokens they +want, but we want to simplify things for now. + +```rust,noplayground +# use cosmwasm_std::Addr; +# use serde::{Deserialize, Serialize}; +# +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct InstantiateMsg { + pub admins: Vec, + pub donation_denom: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum ExecuteMsg { + AddMembers { admins: Vec }, + Leave {}, + Donate {}, +} +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct GreetResp { +# pub message: String, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct AdminsListResp { +# pub admins: Vec, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub enum QueryMsg { +# Greet {}, +# AdminsList {}, +# } +``` + +We also need to add a new state part, to keep the `donation_denom`: + +```rust,noplayground +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const ADMINS: Item> = Item::new("admins"); +pub const DONATION_DENOM: Item = Item::new("donation_denom"); +``` + +And instantiate it properly: + +```rust,noplayground +# use crate::error::ContractError; +# 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, +# }; + +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let admins: StdResult> = msg + .admins + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect(); + ADMINS.save(deps.storage, &admins?)?; + DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; + + Ok(Response::new()) +} +# +# 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)?), +# } +# } +# +# pub fn execute( +# deps: DepsMut, +# _env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# use ExecuteMsg::*; +# +# match msg { +# AddMembers { admins } => exec::add_members(deps, info, admins), +# Leave {} => exec::leave(deps, info).map_err(Into::into), +# } +# } +# +# mod exec { +# use super::*; +# +# pub fn add_members( +# deps: DepsMut, +# info: MessageInfo, +# admins: Vec, +# ) -> Result { +# let mut curr_admins = ADMINS.load(deps.storage)?; +# if !curr_admins.contains(&info.sender) { +# 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()); +# +# let admins: StdResult> = admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# +# curr_admins.append(&mut admins?); +# ADMINS.save(deps.storage, &curr_admins)?; +# +# Ok(resp) +# } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# +# #[test] +# fn unauthorized() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let err = app +# .execute_contract( +# Addr::unchecked("user"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap_err(); +# +# assert_eq!( +# ContractError::Unauthorized { +# sender: Addr::unchecked("user") +# }, +# err.downcast().unwrap() +# ); +# } +# +# #[test] +# fn add_members() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["owner".to_owned()], +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp = app +# .execute_contract( +# Addr::unchecked("owner"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap(); +# +# let wasm = resp.events.iter().find(|ev| ev.ty == "wasm").unwrap(); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "action") +# .unwrap() +# .value, +# "add_members" +# ); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "added_count") +# .unwrap() +# .value, +# "1" +# ); +# +# let admin_added: Vec<_> = resp +# .events +# .iter() +# .filter(|ev| ev.ty == "wasm-admin_added") +# .collect(); +# assert_eq!(admin_added.len(), 1); +# +# assert_eq!( +# admin_added[0] +# .attributes +# .iter() +# .find(|attr| attr.key == "addr") +# .unwrap() +# .value, +# "user" +# ); +# } +# } +``` + +What also needs some corrections are tests - instantiate messages have a new field. I leave it to you as an exercise. +Now we have everything we need to implement donating funds to admins. First, a minor update to the `Cargo.toml` - we +will use an additional utility crate: + +```toml +[package] +name = "contract" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[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" + +[dev-dependencies] +cw-multi-test = "0.13.4" +cosmwasm-schema = { version = "1.0.0" } +``` + +Then we can implement the donate handler: + +```rust,noplayground +# 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, +}; + +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + use ExecuteMsg::*; + + match msg { + AddMembers { admins } => exec::add_members(deps, info, admins), + Leave {} => exec::leave(deps, info).map_err(Into::into), + Donate {} => exec::donate(deps, info), + } +} + +mod exec { +# use super::*; +# +# pub fn add_members( +# deps: DepsMut, +# info: MessageInfo, +# admins: Vec, +# ) -> Result { +# let mut curr_admins = ADMINS.load(deps.storage)?; +# if !curr_admins.contains(&info.sender) { +# 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()); +# +# let admins: StdResult> = admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# +# curr_admins.append(&mut admins?); +# ADMINS.save(deps.storage, &curr_admins)?; +# +# Ok(resp) +# } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +# + pub fn donate(deps: DepsMut, info: MessageInfo) -> Result { + let denom = DONATION_DENOM.load(deps.storage)?; + let admins = ADMINS.load(deps.storage)?; + + let donation = cw_utils::must_pay(&info, &denom)?.u128(); + + let donation_per_admin = donation / (admins.len() as u128); + + let messages = admins.into_iter().map(|admin| BankMsg::Send { + to_address: admin.to_string(), + amount: coins(donation_per_admin, &denom), + }); + + let resp = Response::new() + .add_messages(messages) + .add_attribute("action", "donate") + .add_attribute("amount", donation.to_string()) + .add_attribute("per_admin", donation_per_admin.to_string()); + + Ok(resp) + } +} +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# +# #[test] +# fn unauthorized() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let err = app +# .execute_contract( +# Addr::unchecked("user"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap_err(); +# +# assert_eq!( +# ContractError::Unauthorized { +# sender: Addr::unchecked("user") +# }, +# err.downcast().unwrap() +# ); +# } +# +# #[test] +# fn add_members() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["owner".to_owned()], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp = app +# .execute_contract( +# Addr::unchecked("owner"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap(); +# +# let wasm = resp.events.iter().find(|ev| ev.ty == "wasm").unwrap(); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "action") +# .unwrap() +# .value, +# "add_members" +# ); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "added_count") +# .unwrap() +# .value, +# "1" +# ); +# +# let admin_added: Vec<_> = resp +# .events +# .iter() +# .filter(|ev| ev.ty == "wasm-admin_added") +# .collect(); +# assert_eq!(admin_added.len(), 1); +# +# assert_eq!( +# admin_added[0] +# .attributes +# .iter() +# .find(|attr| attr.key == "addr") +# .unwrap() +# .value, +# "user" +# ); +# } +# } +``` + +Sending the funds to another contract is performed by adding bank messages to +the response. The blockchain would expect any message which is returned in +contract response as a part of an execution. This design is related to an actor +model implemented by CosmWasm. The whole actor model will be described in +detail later. For now, you can assume this is a way to handle token transfers. +Before sending tokens to admins, we have to calculate the amount of donation +per admin. It is done by searching funds for an entry describing our donation +token and dividing the number of tokens sent by the number of admins. Note that +because the integral division is always rounding down. + +As a consequence, it is possible that not all tokens sent as a donation would +end up with no admins accounts. Any leftover would be left on our contract +account forever. There are plenty of ways of dealing with this issue - figuring +out one of them would be a great exercise. + +The last missing part is updating the `ContractError` - the `must_pay` call +returns a `cw_utils::PaymentError` which we can't convert to our error type +yet: + +```rust,noplayground +use cosmwasm_std::{Addr, StdError}; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + StdError(#[from] StdError), + #[error("{sender} is not contract admin")] + Unauthorized { sender: Addr }, + #[error("Payment error: {0}")] + Payment(#[from] PaymentError), +} +``` + +As you can see, to handle incoming funds, I used the utility function - I +encourage you to take a look at [its +implementation](https://docs.rs/cw-utils/0.13.4/src/cw_utils/payment.rs.html#32-39) - +this would give you a good understanding of how incoming funds are structured +in `MessageInfo`. + +Now it's time to check if the funds are distributed correctly. The way for that +is to write a test. + +```rust,noplayground +# 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, +# }; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# pub fn execute( +# deps: DepsMut, +# _env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# use ExecuteMsg::*; +# +# match msg { +# AddMembers { admins } => exec::add_members(deps, info, admins), +# Leave {} => exec::leave(deps, info).map_err(Into::into), +# Donate {} => exec::donate(deps, info), +# } +# } +# +# mod exec { +# use super::*; +# +# pub fn add_members( +# deps: DepsMut, +# info: MessageInfo, +# admins: Vec, +# ) -> Result { +# let mut curr_admins = ADMINS.load(deps.storage)?; +# if !curr_admins.contains(&info.sender) { +# 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()); +# +# let admins: StdResult> = admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# +# curr_admins.append(&mut admins?); +# ADMINS.save(deps.storage, &curr_admins)?; +# +# Ok(resp) +# } +# +# pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { +# ADMINS.update(deps.storage, move |admins| -> StdResult<_> { +# let admins = admins +# .into_iter() +# .filter(|admin| *admin != info.sender) +# .collect(); +# Ok(admins) +# })?; +# +# Ok(Response::new()) +# } +# +# pub fn donate(deps: DepsMut, info: MessageInfo) -> Result { +# let denom = DONATION_DENOM.load(deps.storage)?; +# let admins = ADMINS.load(deps.storage)?; +# +# let donation = cw_utils::must_pay(&info, &denom) +# .map_err(|err| StdError::generic_err(err.to_string()))? +# .u128(); +# +# let donation_per_admin = donation / (admins.len() as u128); +# +# let messages = admins.into_iter().map(|admin| BankMsg::Send { +# to_address: admin.to_string(), +# amount: coins(donation_per_admin, &denom), +# }); +# +# let resp = Response::new() +# .add_messages(messages) +# .add_attribute("action", "donate") +# .add_attribute("amount", donation.to_string()) +# .add_attribute("per_admin", donation_per_admin.to_string()); +# +# Ok(resp) +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# #[test] +# fn instantiation() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!(resp, AdminsListResp { admins: vec![] }); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["admin1".to_owned(), "admin2".to_owned()], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract 2", +# None, +# ) +# .unwrap(); +# +# let resp: AdminsListResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::AdminsList {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# AdminsListResp { +# admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], +# } +# ); +# } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# +# #[test] +# fn unauthorized() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec![], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let err = app +# .execute_contract( +# Addr::unchecked("user"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap_err(); +# +# assert_eq!( +# ContractError::Unauthorized { +# sender: Addr::unchecked("user") +# }, +# err.downcast().unwrap() +# ); +# } +# +# #[test] +# fn add_members() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { +# admins: vec!["owner".to_owned()], +# donation_denom: "eth".to_owned(), +# }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp = app +# .execute_contract( +# Addr::unchecked("owner"), +# addr, +# &ExecuteMsg::AddMembers { +# admins: vec!["user".to_owned()], +# }, +# &[], +# ) +# .unwrap(); +# +# let wasm = resp.events.iter().find(|ev| ev.ty == "wasm").unwrap(); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "action") +# .unwrap() +# .value, +# "add_members" +# ); +# assert_eq!( +# wasm.attributes +# .iter() +# .find(|attr| attr.key == "added_count") +# .unwrap() +# .value, +# "1" +# ); +# +# let admin_added: Vec<_> = resp +# .events +# .iter() +# .filter(|ev| ev.ty == "wasm-admin_added") +# .collect(); +# assert_eq!(admin_added.len(), 1); +# +# assert_eq!( +# admin_added[0] +# .attributes +# .iter() +# .find(|attr| attr.key == "addr") +# .unwrap() +# .value, +# "user" +# ); +# } +# + #[test] + fn donations() { + let mut app = App::new(|router, _, storage| { + router + .bank + .init_balance(storage, &Addr::unchecked("user"), coins(5, "eth")) + .unwrap() + }); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { + admins: vec!["admin1".to_owned(), "admin2".to_owned()], + donation_denom: "eth".to_owned(), + }, + &[], + "Contract", + None, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked("user"), + addr.clone(), + &ExecuteMsg::Donate {}, + &coins(5, "eth"), + ) + .unwrap(); + + assert_eq!( + app.wrap() + .query_balance("user", "eth") + .unwrap() + .amount + .u128(), + 0 + ); + + assert_eq!( + app.wrap() + .query_balance(&addr, "eth") + .unwrap() + .amount + .u128(), + 1 + ); + + assert_eq!( + app.wrap() + .query_balance("admin1", "eth") + .unwrap() + .amount + .u128(), + 2 + ); + + assert_eq!( + app.wrap() + .query_balance("admin2", "eth") + .unwrap() + .amount + .u128(), + 2 + ); + } +} +``` + +Fairly simple. I don't particularly appreciate that every balance check is +eight lines of code, but it can be improved by enclosing this assertion into a +separate function, probably with the +[`#[track_caller]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-track_caller-attribute) +attribute. + +The critical thing to talk about is how `app` creation changed. Because we need +some initial tokens on a `user` account, instead of using the default +constructor, we have to provide it with an initializer function. Unfortunately, +[`new`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#method.new) +documentation is not easy to follow - even if a function is not very +complicated. What it takes as an argument is a closure with three arguments - +the +[`Router`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.Router.html) +with all modules supported by multi-test, the API object, and the state. This +function is called once during contract instantiation. The `router` object +contains some generic fields - we are interested in `bank` in particular. It +has a type of +[`BankKeeper`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.BankKeeper.html), +where the +[`init_balance`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.BankKeeper.html#method.init_balance) +function sits. + +## Plot Twist! + +As we covered most of the important basics about building Rust smart contracts, I have a serious exercise for you. + +The contract we built has an exploitable bug. All donations are distributed equally across admins. However, every +admin is eligible to add another admin. And nothing is preventing the admin from adding himself to the list and +receiving twice as many rewards as others! + +Try to write a test that detects such a bug, then fix it and ensure the bug nevermore occurs. + +Even if the admin cannot add the same address to the list, he can always create new accounts and add them, but this +is something unpreventable on the contract level, so do not prevent that. Handling this kind of case is done by +properly designing whole applications, which is out of this chapter's scope. diff --git a/src/basics/good-practices.md b/src/basics/good-practices.md new file mode 100644 index 0000000..06261a0 --- /dev/null +++ b/src/basics/good-practices.md @@ -0,0 +1,365 @@ +# Good practices + +All the relevant basics are covered. Now let's talk about some good practices. + +## JSON renaming + +Due to Rust style, all our message variants are spelled in a +[camel-case](https://en.wikipedia.org/wiki/CamelCase). It is standard practice, +but it has a drawback - all messages are serialized and deserialized by serde +using those variant names. The problem is that it is more common to use [snake +cases](https://en.wikipedia.org/wiki/Snake_case) for field names in the JSON +world. Fortunately, there is an effortless way to tell serde, to change the names +casing for serialization purposes. Let's update our messages with a `#[serde]` +attribute: + +```rust,noplayground +use cosmwasm_std::Addr; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct InstantiateMsg { + pub admins: Vec, + pub donation_denom: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + AddMembers { admins: Vec }, + Leave {}, + Donate {}, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct GreetResp { + pub message: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub struct AdminsListResp { + pub admins: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Greet {}, + AdminsList {}, +} +``` + +## JSON schema + +Talking about JSON API, it is worth mentioning JSON Schema. It is a way of defining a shape for JSON messages. +It is good practice to provide a way to generate schemas for contract API. The problem is that writing JSON +schemas by hand is a pain. The good news is that there is a crate that would help us with that. Go to the `Cargo.toml`: + +```toml +[package] +name = "contract" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[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" + +[dev-dependencies] +cw-multi-test = "0.13.4" +``` + +There is one additional change in this file - in `crate-type` I added "rlib". "cdylib" crates cannot be used as typical +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: + +```rust,noplayground +# use cosmwasm_std::Addr; +use schemars::JsonSchema; +# use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InstantiateMsg { + pub admins: Vec, + pub donation_denom: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + AddMembers { admins: Vec }, + Leave {}, + Donate {}, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct GreetResp { + pub message: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct AdminsListResp { + pub admins: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Greet {}, + AdminsList {}, +} +``` + +You may argue that all those derives look slightly clunky, and I agree. +Fortunately, the +[`cosmwasm-schema`](https://docs.rs/cosmwasm-schema/1.1.4/cosmwasm_schema/#) +crate delivers a utility `cw_serde` macro, which we can use to reduce a +boilerplate: + +```rust,noplayground +# use cosmwasm_std::Addr; +use cosmwasm_schema::cw_serde + +#[cw_serde] +pub struct InstantiateMsg { + pub admins: Vec, + pub donation_denom: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + AddMembers { admins: Vec }, + Leave {}, + Donate {}, +} + +#[cw_serde] +pub struct GreetResp { + pub message: String, +} + +#[cw_serde] +pub struct AdminsListResp { + pub admins: Vec, +} + +#[cw_serde] +pub enum QueryMsg { + Greet {}, + AdminsList {}, +} +``` + +Additionally, we have to derive the additional `QueryResponses` trait for our +query message to correlate the message variants with responses we would +generate for them: + +```rust,noplayground +# use cosmwasm_std::Addr; +use cosmwasm_schema::{cw_serde, QueryResponses} + +# #[cw_serde] +# pub struct InstantiateMsg { +# pub admins: Vec, +# pub donation_denom: String, +# } +# +# #[cw_serde] +# pub enum ExecuteMsg { +# AddMembers { admins: Vec }, +# Leave {}, +# Donate {}, +# } +# +# #[cw_serde] +# pub struct GreetResp { +# pub message: String, +# } +# +# #[cw_serde] +# pub struct AdminsListResp { +# pub admins: Vec, +# } +# +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GreetResp)] + Greet {}, + #[returns(AdminsListResp)] + AdminsList {}, +} +``` + +The `QueryResponses` is a trait that requires the `#[returns(...)]` attribute +to all your query variants to generate additional information about the +query-response relationship. + +Now, we want to make the `msg` module public and accessible by crates depending +on our contract (in this case - for schema example). Update a `src/lib.rs`: + +```rust,noplayground +# use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use error::ContractError; +# use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +# +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; +# +# #[entry_point] +# pub fn instantiate( +# deps: DepsMut, +# env: Env, +# info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# contract::instantiate(deps, env, info, msg) +# } +# +# #[entry_point] +# pub fn execute( +# deps: DepsMut, +# env: Env, +# info: MessageInfo, +# msg: ExecuteMsg, +# ) -> Result { +# contract::execute(deps, env, info, msg) +# } +# +# #[entry_point] +# pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +# contract::query(deps, env, msg) +# } +``` + +I changed the visibility of all modules - as our crate can now be used as a dependency. +If someone would like to do so, he may need access to handlers or state. + +The next step is to create a tool generating actual schemas. We will do it by creating +an binary in our crate. Create a new `bin/schema.rs` file: + +```rust,noplayground +use contract::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_schema::write_api; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg + } +} +``` + +Cargo is smart enough to recognize files in `src/bin` directory as utility +binaries for the crate. Now we can generate our schemas: + +``` +$ cargo run schema + Finished dev [unoptimized + debuginfo] target(s) in 0.52s + Running `target/debug/schema schema` +Removing "/home/hashed/confio/git/book/examples/03-basics/schema/contract.json" … +Exported the full API as /home/hashed/confio/git/book/examples/03-basics/schema/contract.json +``` + +I encourage you to go to generated file to see what the schema looks like. + + +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: + +```toml +[alias] +wasm = "build --target wasm32-unknown-unknown --release --lib" +wasm-debug = "build --target wasm32-unknown-unknown --lib" +schema = "run schema" +``` + +The `--lib` flag added to `wasm` cargo aliases tells the toolchain to build +only the library target - it would skip building any binaries. Additionally, I +added the convenience `schema` alias so that one can generate schema calling +simply `cargo schema`. + +## Disabling entry points for libraries + +Since we added the "rlib" target for the contract, it is, as mentioned before, useable as a dependency. +The problem is that the contract depended on ours would have Wasm entry points generated twice - once +in the dependency and once in the final contract. We can work this around by disabling generating Wasm +entry points for the contract if the crate is used as a dependency. We would use +[feature flags](https://doc.rust-lang.org/cargo/reference/features.html) for that. + +Start with updating `Cargo.toml`: + +```toml +[features] +library = [] +``` + +This way, we created a new feature for our crate. Now we want to disable the `entry_point` attribute on +entry points - we will do it by a slight update of `src/lib.rs`: + +```rust,noplayground +# use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +# use error::ContractError; +# use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +# +# pub mod contract; +# pub mod error; +# pub mod msg; +# pub mod state; +# +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + contract::instantiate(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + contract::execute(deps, env, info, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + contract::query(deps, env, msg) +} +``` + +The [`cfg_attr`](https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute) attribute is +a conditional compilation attribute, similar to the `cfg` we used before for the test. It expands to the given attribute if +the condition expands to true. In our case - it would expand to nothing if the feature "library" is enabled, or it +would expand just to `#[entry_point]` in another case. + +Since now to add this contract as a dependency, don't forget to enable the feature like this: + +```toml +[dependencies] +my_contract = { version = "0.1", features = ["library"] } +``` diff --git a/src/basics/multitest-intro.md b/src/basics/multitest-intro.md new file mode 100644 index 0000000..ff22ec0 --- /dev/null +++ b/src/basics/multitest-intro.md @@ -0,0 +1,160 @@ +# Introducing multitest + +Let me introduce the [`multitest`](https://crates.io/crates/cw-multi-test) - +library for creating tests for smart contracts in Rust. + +The core idea of `multitest` is abstracting an entity of contract and +simulating the blockchain environment for testing purposes. The purpose of this +is to be able to test communication between smart contracts. It does its job +well, but it is also an excellent tool for testing single-contract scenarios. + +First, we need to add a multitest to our `Cargo.toml`. + +```toml +[package] +name = "contract" +version = "0.1.0" +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"] } + +[dev-dependencies] +cw-multi-test = "0.13.4" +``` + +I added a new +[`[dev-dependencies]`](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies) +section with dependencies not used by the final binary +but which may be used by tools around the development process - for example, tests. + +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, +# }; +# +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +# pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +# use QueryMsg::*; +# +# match msg { +# Greet {} => to_binary(&query::greet()?), +# } +# } +# +#[allow(dead_code)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty +) -> StdResult { + unimplemented!() +} + +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { + use cosmwasm_std::Addr; + use cw_multi_test::{App, ContractWrapper, Executor}; + + use super::*; + + #[test] + fn greet_query() { + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &Empty {}, + &[], + "Contract", + None, + ) + .unwrap(); + + let resp: GreetResp = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::Greet {}) + .unwrap(); + + assert_eq!( + resp, + GreetResp { + message: "Hello World".to_owned() + } + ); + } +} +``` + +You probably notice that I added the function for an `execute` entry point. I didn't add the entry point +itself or the function's implementation, but for the multitest purposes contract has to contain at least +instantiate, query, and execute handlers. I attributed the function as +[`#[allow(dead_code)]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes), +so, `cargo` will not complain about it not being used anywhere. Enabling it for tests only with `#[cfg(test)]` +would also be a way. + +Then at the beginning of the test, I created the +[`App`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#) +object. It is a core multitest entity representing the virtual blockchain on +which we will run our contracts. As you can see, we can call functions on it +just like we would interact with blockchain using `wasmd`! + +Right after creating `app`, I prepared the representation of the `code`, which +would be "uploaded" to the blockchain. As multitests are just native Rust +tests, they do not involve any Wasm binaries, but this name matches well what +happens in a real-life scenario. We store this object in the blockchain with +the [`store_code`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html#method.store_code) +function, and as a result, we are getting the code id - we would need it to +instantiate a contract. + +Instantiation is the next step. In a single +[`instantiate_contract`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/trait.Executor.html#method.instantiate_contract) +call, we provide everything we would provide via `wasmd` - the contract code id, the address which performs instantiation, + +the message triggering it, and any funds sent with the message (again - empty for now). We are adding the contract label +and its admin for migrations - `None`, as we don't need it yet. + +And after the contract is online, we can query it. The +[`wrap`](https://docs.rs/cw-multi-test/0.13.4/cw_multi_test/struct.App.html?search=in#method.wrap) function is an accessor +for querying Api (queries are handled a bit differently than other calls), and the +[`query_wasm_smart`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.QuerierWrapper.html#method.query_wasm_smart) +queries are given a contract with the message. Also, we don't need to care about query results as `Binary` - multitest +assumes that we would like to deserialize them to some response type, so it takes advantage of Rust type elision to +provide us with a nice Api. + +Now it's time to rerun the test. It should still pass, but now we nicely abstracted the testing contract as a whole, +not some internal functions. The next thing we should probably cover is making the contract more interesting +by adding some state. diff --git a/src/basics/query-testing.md b/src/basics/query-testing.md new file mode 100644 index 0000000..c9e54db --- /dev/null +++ b/src/basics/query-testing.md @@ -0,0 +1,281 @@ +# Testing a query + +Last time we created a new query, now it is time to test it out. We will start with the basics - +the unit test. This approach is simple and doesn't require knowledge besides Rust. Go to the +`src/contract.rs` and add a test in its module: + +```rust,noplayground +# use crate::msg::{GreetResp, QueryMsg}; +# use cosmwasm_std::{ +# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +# pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +# use QueryMsg::*; +# +# match msg { +# Greet {} => to_binary(&query::greet()?), +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn greet_query() { + let resp = query::greet().unwrap(); + assert_eq!( + resp, + GreetResp { + message: "Hello World".to_owned() + } + ); + } +} +``` + +If you ever wrote a unit test in Rust, nothing should surprise you here. Just a +simple test-only module contains local function unit tests. The problem is - this +test doesn't build yet. We need to tweak our message types a bit. Update the `src/msg.rs`: + +```rust,noplayground +# use serde::{Deserialize, Serialize}; +# +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct GreetResp { + pub message: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum QueryMsg { + Greet {}, +} +``` + +I added three new derives to both message types. [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) +is required to allow comparing types +for equality - so we can check if they are equal. The [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) +is a trait generating debug-printing +utilities. It is used by [`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html) to +display information about mismatch if an assertion +fails. Note that because we are not testing the `QueryMsg` in any way, the additional trait derives +are optional. Still, it is a good practice to make all messages both `PartialEq` and `Debug` for +testability and consistency. +The last one, [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) is not needed for now yet, +but it is also good practice to allow messages to be cloned around. We will also require that +later, so I added it already not to go back and forth. + +Now we are ready to run our test: + +``` +$ cargo test + +... +running 1 test +test contract::tests::greet_query ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +Yay! Test passed! + +## Contract as a black box + +Now let's go a step further. The Rust testing utility is a friendly tool for building even higher-level +tests. We are currently testing smart contract internals, but if you think about how your smart contract +is visible from the outside world. It is a single entity that is triggered by some input messages. We can +create tests that treat the whole contract as a black box by testing it via our `query` function. Let's +update our test: + +```rust,noplayground +# use crate::msg::{GreetResp, QueryMsg}; +# use cosmwasm_std::{ +# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +# pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +# use QueryMsg::*; +# +# match msg { +# Greet {} => to_binary(&query::greet()?), +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { + use cosmwasm_std::from_binary; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + + use super::*; + + #[test] + fn greet_query() { + let resp = query( + mock_dependencies().as_ref(), + mock_env(), + QueryMsg::Greet {} + ).unwrap(); + let resp: GreetResp = from_binary(&resp).unwrap(); + + assert_eq!( + resp, + GreetResp { + message: "Hello World".to_owned() + } + ); + } +} +``` + +We needed to produce two entities for the `query` functions: the `deps` and `env` instances. +Fortunately, `cosmwasm-std` provides utilities for testing those - +[`mock_dependencies`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_dependencies.html) +and [`mock_env`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_env.html) +functions. + +You may notice the dependencies mock of a type +[`OwnedDeps`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html) instead +of `Deps`, which we need here - this is why the +[`as_ref`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html#method.as_ref) +function is called on it. If we looked for a `DepsMut` object, we would use +[`as_mut`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.OwnedDeps.html#method.as_mut) +instead. + +We can rerun the test, and it should still pass. But when we think about that test reflecting +the actual use case, it is inaccurate. The contract is queried, but it was never instantiated! +In software engineering, it is equivalent to calling a getter without constructing an object - +taking it out of nowhere. It is a lousy testing approach. We can do better: + +```rust,noplayground + +# use crate::msg::{GreetResp, QueryMsg}; +# use cosmwasm_std::{ +# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +# pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +# use QueryMsg::*; +# +# match msg { +# Greet {} => to_binary(&query::greet()?), +# } +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { + use cosmwasm_std::from_binary; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + + use super::*; + + #[test] + fn greet_query() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + instantiate( + deps.as_mut(), + env.clone(), + mock_info("sender", &[]), + Empty {}, + ) + .unwrap(); + + let resp = query(deps.as_ref(), env, QueryMsg::Greet {}).unwrap(); + let resp: GreetResp = from_binary(&resp).unwrap(); + assert_eq!( + resp, + GreetResp { + message: "Hello World".to_owned() + } + ); + } +} +``` + +A couple of new things here. First, I extracted the `deps` and `env` variables to their variables +and passed them to calls. The idea is that those variables represent some blockchain persistent state, +and we don't want to create them for every call. We want any changes to the contract state occurring +in `instantiate` to be visible in the `query`. Also, we want to control how the environment differs +on the query and instantiation. + +The `info` argument is another story. The message info is unique for each message sent. To create the +`info` mock, we must pass two arguments to the +[`mock_info`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/testing/fn.mock_info.html) function. + +First is the address performing a call. It may look strange to pass `sender` as an address instead of some +mysterious `wasm` followed by hash, but it is a valid address. For testing purposes, such addresses are +typically better, as they are way more verbose in case of failing tests. + +The second argument is funds sent with the message. For now, we leave it as an empty slice, as I don't want +to talk about token transfers yet - we will cover it later. + +So now it is more a real-case scenario. I see just one problem. I say that the contract is a single black +box. But here, nothing connects the `instantiate` call to the corresponding `query`. It seems that we assume +there is some global contract. But it seems that if we would like to have two contracts instantiated differently +in a single test case, it would become a mess. If only there would be some tool to abstract this for us, wouldn't +it be nice? diff --git a/src/basics/query.md b/src/basics/query.md new file mode 100644 index 0000000..5d18c63 --- /dev/null +++ b/src/basics/query.md @@ -0,0 +1,333 @@ +# Creating a query + +We have already created a simple contract reacting to an empty instantiate message. Unfortunately, it +is not very useful. Let's make it a bit reactive. + +First, we need to add [`serde`](https://crates.io/crates/serde) crate to our dependencies. It would help us with the serialization and +deserialization of query messages. Update the `Cargo.toml`: + +```toml +[package] +name = "contract" +version = "0.1.0" +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"] } + +[dev-dependencies] +cw-multi-test = "0.13.4" +``` + +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, + Response, StdResult, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +struct QueryResp { + message: String, +} + +# #[entry_point] +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +#[entry_point] +pub fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { + let resp = QueryResp { + message: "Hello World".to_owned(), + }; + + to_binary(&resp) +} +``` + +Note that I omitted the previously created instantiate endpoint for simplicity - +not to overload you with code, I will always only show lines that changed in the code. + +We first need a structure we will return from our query. We always want to return something +which is serializable. We are just deriving the +[`Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and +[`Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html) traits from `serde` crate. + +Then we need to implement our entry point. It is very similar to the `instantiate` one. The +first significant difference is a type of `deps` argument. For `instantiate`, it was a `DepMut`, +but here we went with a [`Deps`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/struct.Deps.html) +object. That is because the query can never alter the smart +contract's internal state. It can only read the state. It comes with some consequences - for example, +it is impossible to implement caching for future queries (as it would require some data cache to write +to). + +The other difference is the lack of the `info` argument. The reason here is that the entry point which +performs actions (like instantiation or execution) can differ how an action is performed based on the +message metadata - for example, they can limit who can perform an action (and do so by checking the +message `sender`). It is not a case for queries. Queries are supposed just purely to return some +transformed contract state. It can be calculated based on some chain metadata (so the state can +"automatically" change after some time), but not on message info. + +Note that our entry point still has the same `Empty` type for its `msg` argument - it means that the +query message we would send to the contract is still an empty JSON: `{}` + +The last thing that changed is the return type. Instead of returning the `Response` type on the success +case, we return an arbitrary serializable object. This is because queries are not using a typical actor +model message flow - they cannot trigger any actions nor communicate with other contracts in ways different +than querying them (which is handled by the `deps` argument). The query always returns plain data, which +should be presented directly to the querier. + +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. + +## Improving the message + +We have a query, but there is a problem with the query message. It is always an empty JSON. It is terrible - +if we would like to add another query in the future, it would be difficult to have any reasonable distinction +between query variants. + +In practice, we address this by using a non-empty query message type. Improve our contract: + +```rust,noplayground +# use cosmwasm_std::{ +# entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# use serde::{Deserialize, Serialize}; +# +# #[derive(Serialize, Deserialize)] +# struct QueryResp { +# message: String, +# } +# +#[derive(Serialize, Deserialize)] +pub enum QueryMsg { + Greet {}, +} + +# #[entry_point] +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +#[entry_point] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + use QueryMsg::*; + + match msg { + Greet {} => { + let resp = QueryResp { + message: "Hello World".to_owned(), + }; + + to_binary(&resp) + } + } +} +``` + +Now we introduced a proper message type for the query message. It is an +[enum](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html), and by +default, it would serialize to a JSON with a single field - the name of the field +will be an enum variant (in our case - always "greet" - at least for now), and the +value of this field would be an object assigned to this enum variant. + +Note that our enum has no type assigned to the only `Greet` variant. Typically +in Rust, we create such variants without additional `{}` after the variant name. Here the +curly braces have a purpose - without them, the variant would serialize to just a string +type - so instead of `{ "greet": {} }`, the JSON representation of this variant would be +`"greet"`. This behavior brings inconsistency in the message schema. It is, generally, +a good habit to always add the `{}` to serde serializable empty enum variants - for better +JSON representation. + +But now, we can still improve the code further. Right now, the `query` function has two +responsibilities. The first is obvious - handling the query itself. It was the first +assumption, and it is still there. But there is a new thing happening there - the query +message dispatching. It may not be obvious, as there is a single variant, but the query +function is an excellent way to become a massive unreadable `match` statement. To make +the code more [SOLID](https://en.wikipedia.org/wiki/SOLID), we will refactor it and +take out handling the `greet` message to a +separate function. + +```rust,noplayground +# use cosmwasm_std::{ +# entry_point, to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# use serde::{Deserialize, Serialize}; +# +#[derive(Serialize, Deserialize)] +pub struct GreetResp { + message: String, +} + +# #[derive(Serialize, Deserialize)] +# pub enum QueryMsg { +# Greet {}, +# } +# +# #[entry_point] +# pub fn instantiate( +# _deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# _msg: Empty, +# ) -> StdResult { +# Ok(Response::new()) +# } +# +#[entry_point] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + use QueryMsg::*; + + match msg { + Greet {} => to_binary(&query::greet()?), + } +} + +mod query { + use super::*; + + pub fn greet() -> StdResult { + let resp = GreetResp { + message: "Hello World".to_owned(), + }; + + Ok(resp) + } +} +``` + +Now it looks much better. Note there are a couple of additional improvements. I +renamed the query-response type `GreetResp` as I may have different responses +for different queries. I want the name to relate only to the variant, not the +whole message. + +Next is enclosing my new function in the module `query`. It makes it easier to +avoid name collisions - I can have the same variant for queries and execution +messages in the future, and their handlers would lie in separate namespaces. + +A questionable decision may be returning `StdResult` instead of `GreetResp` +from `greet` function, as it would never return an error. It is a matter of +style, but I prefer consistency over the message handler, and the majority of +them would have failure cases - e.g. when reading the state. + +Also, you might pass `deps` and `env` arguments to all your query handlers for +consistency. I'm not too fond of this, as it introduces unnecessary boilerplate +which doesn't read well, but I also agree with the consistency argument - I +leave it to your judgment. + +## Structuring the contract + +You can see that our contract is becoming a bit bigger now. About 50 lines are maybe +not so much, but there are many different entities in a single file, and I think we +can do better. I can already distinguish three different types of entities in the code: +entry points, messages, and handlers. In most contracts, we would divide them across +three files. Start with extracting all the messages to `src/msg.rs`: + +```rust,noplayground +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct GreetResp { + pub message: String, +} + +#[derive(Serialize, Deserialize)] +pub enum QueryMsg { + Greet {}, +} +``` + +You probably noticed that I made my `GreetResp` fields public. It is because they have +to be now accessed from a different module. Now move forward to the `src/contract.rs` file: + +```rust,noplayground +use crate::msg::{GreetResp, QueryMsg}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; + +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, +) -> StdResult { + Ok(Response::new()) +} + +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + use QueryMsg::*; + + match msg { + Greet {} => to_binary(&query::greet()?), + } +} + +mod query { + use super::*; + + pub fn greet() -> StdResult { + let resp = GreetResp { + message: "Hello World".to_owned(), + }; + + Ok(resp) + } +} +``` + +I moved most of the logic here, so my `src/lib.rs` is just a very thin library entry with nothing +else but module definitions and entry points definition. I removed the `#[entry_point]` attribute +from my `query` function in `src/contract.rs`. I will have a function with this attribute. +Still, I wanted to split functions' responsibility further - not the `contract::query` function is +the top-level query handler responsible for dispatching the query message. The `query` function on +crate-level is only an entry point. It is a subtle distinction, but it will make sense in the future +when we would like not to generate the entry points but to keep the dispatching functions. I introduced +the split now to show you the typical contract structure. + +Now the last part, the `src/lib.rs` file: + +```rust,noplayground +use cosmwasm_std::{ + entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; + +mod contract; +mod msg; + +#[entry_point] +pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: Empty) + -> StdResult +{ + contract::instantiate(deps, env, info, msg) +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) + -> StdResult +{ + contract::query(deps, env, msg) +} +``` + +Straightforward top-level module. Definition of submodules and entry points, nothing more. + +Now, when we have the contract ready to do something, let's go and test it. diff --git a/src/basics/rust-project.md b/src/basics/rust-project.md new file mode 100644 index 0000000..a0beaee --- /dev/null +++ b/src/basics/rust-project.md @@ -0,0 +1,35 @@ +# Create a Rust project + +As smart contracts are Rust library crates, we will start with creating one: + +``` +$ cargo new --lib ./empty-contract +``` + +You created a simple Rust library, but it is not yet ready to be a smart contract. The first thing +to do is to update the `Cargo.toml` file: + +```toml +[package] +name = "contract" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } + +``` + +As you can see, I added a `crate-type` field for the library section. Generating the `cdylib` is +required to create a proper web assembly binary. The downside of this is that such a library cannot +be used as a dependency for other Rust crates - for now, it is not needed, but later we will show +how to approach reusing contracts as dependencies. + +Additionally, there is one core dependency for smart contracts: the `cosmwasm-std`. This crate is a +standard library for smart contracts. It provides essential utilities for communication with the +outside world and a couple of helper functions and types. Every smart contract we will build will +use this dependency. + diff --git a/src/basics/state.md b/src/basics/state.md new file mode 100644 index 0000000..a3b4c1a --- /dev/null +++ b/src/basics/state.md @@ -0,0 +1,659 @@ +# Contract state + +The contract we are working on already has some behavior that can answer a query. Unfortunately, it is +very predictable with its answers, and it has nothing to alter them. In this chapter, I introduce the +notion of state, which would allow us to bring true life to a smart contract. + +The state would still be static for now - it would be initialized on contract instantiation. The state +would contain a list of admins who would be eligible to execute messages in the future. + +The first thing to do is to update `Cargo.toml` with yet another dependency - the +[`storage-plus`](https://crates.io/crates/cw-storage-plus) crate with high-level bindings for CosmWasm +smart contracts state management: + +```toml +[package] +name = "contract" +version = "0.1.0" +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" + +[dev-dependencies] +cw-multi-test = "0.13.4" +``` + +Now create a new file where you will keep a state for the contract - we typically call it `src/state.rs`: + +```rust,noplayground +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const ADMINS: Item> = Item::new("admins"); +``` + +And make sure we declare the module in `src/lib.rs`: + +```rust,noplayground +# use cosmwasm_std::{ +# entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +# mod contract; +# mod msg; +mod state; +# +# #[entry_point] +# pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: Empty) +# -> StdResult +# { +# contract::instantiate(deps, env, info, msg) +# } +# +# #[entry_point] +# pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) +# -> StdResult +# { +# contract::query(deps, env, msg) +# } +``` + +The new thing we have here is the `ADMINS` constant of type `Item>`. You could ask an excellent +question here - how is the state constant? How do I modify it if it is a constant value? + +The answer is tricky - this constant is not keeping the state itself. The state is stored in the +blockchain and is accessed via the `deps` argument passed to entry points. The storage-plus constants +are just accessor utilities helping us access this state in a structured way. + +In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with metainformation +pointing to the contract which owns them (so no other contract can alter them in any way), but even after +removing the prefixes, the single contract state is a smaller key-value pair. + +`storage-plus` handles more complex state structures by additionally prefixing items keys intelligently. For now, +we just used the simplest storage entity - an +[`Item<_>`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html), which holds a single optional +value of a given type - +`Vec` in this case. And what would be a key to this item in the storage? It doesn't matter to us - it would +be figured out to be unique, based on a unique string passed to the +[`new`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.new) function. + +Before we would go into initializing our state, we need some better instantiate message. Go to `src/msg.rs` and create one: + +```rust,noplayground +# use cosmwasm_std::Addr; +# use serde::{Deserialize, Serialize}; +# +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct InstantiateMsg { + pub admins: Vec, +} +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct GreetResp { +# pub message: String, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub enum QueryMsg { +# Greet {}, +# } +``` + +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::state::ADMINS; +// --snip-- +# use cosmwasm_std::{ +# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + let admins: StdResult> = msg + .admins + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect(); + ADMINS.save(deps.storage, &admins?)?; + + Ok(Response::new()) +} +# +# pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +# use QueryMsg::*; +# +# match msg { +# Greet {} => to_binary(&query::greet()?), +# } +# } +# +# #[allow(dead_code)] +# pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult { +# unimplemented!() +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use super::*; +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &Empty {}, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# } +``` + +We also need to update the message type on entry point in `src/lib.rs`: + +```rust,noplayground +# use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use msg::InstantiateMsg; +// --snip-- +# +# mod contract; +# mod msg; +# mod state; +# +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + contract::instantiate(deps, env, info, msg) +} +# +# #[entry_point] +# pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) -> StdResult { +# contract::query(deps, env, msg) +# } +``` + +Voila, that's all that is needed to update the state! + +First, we need to transform the vector of strings into the vector of addresses to be stored. We cannot take +addresses as a message argument because not every string is a valid address. It might be a bit confusing when +we were working on tests. Any string could be used in the place of address. Let me explain. + +Every string can be technically considered an address. However, not every string is an actual existing blockchain +address. When we keep anything of type `Addr` in the contract, we assume it is a proper address in the blockchain. +That is why the [`addr_validate`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/trait.Api.html#tymethod.addr_validate) +function exits - to check this precondition. + +Having data to store, we use the [`save`](https://docs.rs/cw-storage-plus/0.13.4/cw_storage_plus/struct.Item.html#method.save) +function to write it into the contract state. Note that the first argument of `save` is +[`&mut Storage`](https://docs.rs/cosmwasm-std/1.0.0/cosmwasm_std/trait.Storage.html), which is actual blockchain +storage. As emphasized, the `Item` object stores nothing and is just an accessor. It determines how to store the data +in the storage given to it. The second argument is the serializable data to be stored. + +It is a good time to check if the regression we have passes - try running our tests: +``` +> cargo test + +... + +running 1 test +test contract::tests::greet_query ... FAILED + +failures: + +---- contract::tests::greet_query stdout ---- +thread 'contract::tests::greet_query' panicked at 'called `Result::unwrap()` on an `Err` value: error executing WasmMsg: +sender: owner +Instantiate { admin: None, code_id: 1, msg: Binary(7b7d), funds: [], label: "Contract" } + +Caused by: + Error parsing into type contract::msg::InstantiateMsg: missing field `admins`', src/contract.rs:80:14 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + contract::tests::greet_query + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +error: test failed, to rerun pass '--lib' +``` + +Damn, we broke something! But be calm. Let's start with carefully reading an error message: + +> Error parsing into type contract::msg::InstantiateMsg: missing field `admins`', src/contract.rs:80:14 + +The problem is that in the test, we send an empty instantiation message in our test, but right now, our endpoint expects +to have an `admin` field. Multi-test framework tests contract from the entry point to results, so sending messages using MT +functions first serializes them. Then the contract deserializes them on the entry. But now it tries to deserialize the +empty JSON to some non-empty message! We can quickly fix it by updating the test: + +```rust,noplayground +# use crate::msg::{GreetResp, InstantiateMsg, QueryMsg}; +# use crate::state::ADMINS; +# use cosmwasm_std::{ +# to_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# #[allow(dead_code)] +# pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult { +# unimplemented!() +# } +# +# mod query { +# use crate::msg::AdminsListResp; +# +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use super::*; +# + #[test] + fn greet_query() { + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { admins: vec![] }, + &[], + "Contract", + None, + ) + .unwrap(); + + let resp: GreetResp = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::Greet {}) + .unwrap(); + + assert_eq!( + resp, + GreetResp { + message: "Hello World".to_owned() + } + ); + } +# } +``` + +## Testing state + +When the state is initialized, we want a way to test it. We want to provide a +query to check if the instantiation affects the state. Just create a simple one +listing all admins. Start with adding a variant for query message and a corresponding response message in `src/msg.rs`. We'll call the variant `AdminsList`, the response `AdminsListResp`, and have it return a vector of `cosmwasm_std::Addr`: + +```rust,noplayground +# use cosmwasm_std::Addr; +# use serde::{Deserialize, Serialize}; +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct InstantiateMsg { +# pub admins: Vec, +# } +# +# #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +# pub struct GreetResp { +# pub message: String, +# } +# +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct AdminsListResp { + pub admins: Vec, +} + +[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum QueryMsg { + Greet {}, + AdminsList {}, +} +``` + +And implement it in `src/contract.rs`: + +```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, +# }; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +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)?), + } +} + +# #[allow(dead_code)] +# pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult { +# unimplemented!() +# } +# +mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# + pub fn admins_list(deps: Deps) -> StdResult { + let admins = ADMINS.load(deps.storage)?; + let resp = AdminsListResp { admins }; + Ok(resp) + } +} + +# #[cfg(test)] +# mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use super::*; +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +# } +``` + +Now when 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, +# }; +# +# pub fn instantiate( +# deps: DepsMut, +# _env: Env, +# _info: MessageInfo, +# msg: InstantiateMsg, +# ) -> StdResult { +# let admins: StdResult> = msg +# .admins +# .into_iter() +# .map(|addr| deps.api.addr_validate(&addr)) +# .collect(); +# ADMINS.save(deps.storage, &admins?)?; +# +# Ok(Response::new()) +# } +# +# 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)?), +# } +# } +# +# #[allow(dead_code)] +# pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult { +# unimplemented!() +# } +# +# mod query { +# use super::*; +# +# pub fn greet() -> StdResult { +# let resp = GreetResp { +# message: "Hello World".to_owned(), +# }; +# +# Ok(resp) +# } +# +# pub fn admins_list(deps: Deps) -> StdResult { +# let admins = ADMINS.load(deps.storage)?; +# let resp = AdminsListResp { admins }; +# Ok(resp) +# } +# } +# +#[cfg(test)] +mod tests { +# use cosmwasm_std::Addr; +# use cw_multi_test::{App, ContractWrapper, Executor}; +# +# use super::*; +# + #[test] + fn instantiation() { + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { admins: vec![] }, + &[], + "Contract", + None, + ) + .unwrap(); + + let resp: AdminsListResp = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::AdminsList {}) + .unwrap(); + + assert_eq!(resp, AdminsListResp { admins: vec![] }); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { + admins: vec!["admin1".to_owned(), "admin2".to_owned()], + }, + &[], + "Contract 2", + None, + ) + .unwrap(); + + let resp: AdminsListResp = app + .wrap() + .query_wasm_smart(addr, &QueryMsg::AdminsList {}) + .unwrap(); + + assert_eq!( + resp, + AdminsListResp { + admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], + } + ); + } +# +# #[test] +# fn greet_query() { +# let mut app = App::default(); +# +# let code = ContractWrapper::new(execute, instantiate, query); +# let code_id = app.store_code(Box::new(code)); +# +# let addr = app +# .instantiate_contract( +# code_id, +# Addr::unchecked("owner"), +# &InstantiateMsg { admins: vec![] }, +# &[], +# "Contract", +# None, +# ) +# .unwrap(); +# +# let resp: GreetResp = app +# .wrap() +# .query_wasm_smart(addr, &QueryMsg::Greet {}) +# .unwrap(); +# +# assert_eq!( +# resp, +# GreetResp { +# message: "Hello World".to_owned() +# } +# ); +# } +} +``` + +The test is simple - instantiate the contract twice with different initial admins, and ensure the query result +is proper each time. This is often the way we test our contract - we execute bunch o messages on the contract, +and then we query it for some data, verifying if query responses are like expected. + +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/conclusion/conclusion.md b/src/conclusion/conclusion.md deleted file mode 100644 index 4417ab9..0000000 --- a/src/conclusion/conclusion.md +++ /dev/null @@ -1,27 +0,0 @@ -# Conclusion: Charting the Future with CosmWasm - -As we close the pages of this comprehensive exploration into CosmWasm, it's clear that we stand on the precipice of a new era in blockchain development. CosmWasm, with its robust, secure, and interoperable framework, has not only demystified the complexities of smart contract creation but has also paved the way for a future where blockchain's potential is fully realized. - -Throughout this book, we've journeyed from the foundational principles of CosmWasm, delving into the nuances of smart contract development, and exploring the vast possibilities of cross-chain communication via IBC. We've seen how CosmWasm fosters a developer-friendly ecosystem, enabling the creation of complex, scalable, and efficient dApps that can operate across diverse blockchain environments. - -## Reflecting on Key Takeaways - -- **Simplicity and Power:** CosmWasm's design philosophy, which balances simplicity with powerful features, enables developers to craft sophisticated applications without compromising on performance or security. -- **Interoperability:** The integration of IBC protocol showcases CosmWasm's commitment to blockchain interoperability, heralding a future of seamless cross-chain communication and collaboration. -- **Community and Innovation:** The vibrant CosmWasm community, with its spirit of collaboration and innovation, continues to push the boundaries, contributing to the ecosystem's growth and the blockchain landscape at large. - -## Looking Ahead: The Horizon of Possibilities - -As we look to the future, it's evident that CosmWasm will play a pivotal role in shaping the blockchain universe. The flexibility and adaptability of CosmWasm make it well-suited to address the evolving challenges and opportunities within the blockchain space, including: - -- **Enhanced dApp Ecosystem:** As developers harness the power of CosmWasm, we can anticipate a surge in dApp innovation, offering users unprecedented functionalities, user experiences, and value. -- **Cross-Chain dApp Expansion:** With IBC integration, CosmWasm is set to spearhead the development of cross-chain dApps, facilitating a new level of interoperability and fluidity across the blockchain ecosystem. -- **Blockchain Adoption:** By lowering the barrier to entry for smart contract development and fostering a secure, efficient, and interoperable environment, CosmWasm will accelerate blockchain adoption across various sectors. - -## Final Thoughts - -In this book, we've embarked on a journey through the realms of CosmWasm, uncovering its principles, capabilities, and the transformative potential it holds for the blockchain world. As developers, innovators, and visionaries continue to explore and expand the horizons of CosmWasm, the future of blockchain looks brighter than ever. - -The path ahead is filled with opportunities for growth, innovation, and exploration. As the CosmWasm ecosystem evolves, it invites us all to contribute, learn, and shape the future of blockchain technology together. Let us move forward with the knowledge, insights, and inspiration gained from these pages, ready to innovate, collaborate, and drive the blockchain revolution forward. - -In the grand tapestry of blockchain's evolution, CosmWasm stands out as a beacon of progress, interoperability, and community-driven innovation. The journey is far from over; it's just beginning. The future is CosmWasm, and the future is now. Let's build it together. \ No newline at end of file diff --git a/src/cross-contract.md b/src/cross-contract.md new file mode 100644 index 0000000..cb91c1c --- /dev/null +++ b/src/cross-contract.md @@ -0,0 +1,16 @@ +# Cross contract communication + +We already covered creating a single isolating contract. However, SOLID principles tell us that +entities should be as small as reasonably possible - such as they have a +[single responsibility](https://en.wikipedia.org/wiki/Single-responsibility_principle). Entities +we are focusing on now are smart contracts, and we want to make sure that every smart contract has +a sole responsibility it takes care of. + +But we also want to build complex systems using smart contracts. To do so, we need to be able to +communicate between them. We already talked about what such communication looks like using an actor +model. Now it's time to use this knowledge in practice. + +In this chapter, we will improve the previously created administration group model to solve the problem +I brought - the possibility of adding own multiple addresses by a single admin to take bigger donation parts. + +We would also give admins some work to do besides being admins. diff --git a/src/cross-contract/design.md b/src/cross-contract/design.md new file mode 100644 index 0000000..5f8dab5 --- /dev/null +++ b/src/cross-contract/design.md @@ -0,0 +1,149 @@ +# 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. + +First, let's think about the problem we want to solve. Our admins are a vector of addresses. Anyone already +an admin can add anyone he wants to the list. But this "anyone" can be a second instance of the same admin +account, so he counts twice for donations! + +This issue is relatively simple to fix, but there is another problem - as we already learned, the admin could +create a smart contract which he and only he can withdraw tokens from and register as another admin in the +group! Instantiating it multiple times, he can achieve his goal even if we prevent adding the same address +multiple times. There would be many distinct addresses that the same person owns. + +It looks like an unpleasant situation, but there are ways to manage it. The one we would implement is voting. +Instead of being able to add another admin to the list, admins would be allowed to propose their colleagues +as new admins. It would start a voting process - everyone who was an admin at the time of the proposal creation +would be able to support it. If more than half admins would support the new candidate, he would immediately +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: + +```plantuml +@startuml +class admin { + admins: Map + votings: Map + + propose_admin(candidate: Addr) + add_admin() + leave() + donate() + + admins_list() -> Vec + join_time() -> Timestamp +} + +class voting { + votes: Vec + votes_needed: u64 + closed: bool + + accept() + votes_list() -> Vec +} + +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: + +```plantuml +@startuml +actor "Admin 1" as admin1 +actor "Admin 2" as admin2 +actor "Admin 3" as admin3 +entity "Admin Contract" as admin +entity "Votes" as votes + +admin1 -> admin: exec propose_admin { addr: new_admin } + +admin -> votes **: instantiate { addr: "new_admin", required: 3 } + +admin2 -> votes ++: exec accept {} +votes -> admin: query join_time { admin: "admin2" } +admin -> votes: resp join_time_resp { joined: ... } +votes --> votes --: add vote + +admin3 -> votes ++: exec accept {} +votes -> admin: query join_time { admin: "admin3" } +admin -> votes: resp join_time_resp { joined: ... } +votes --> votes: add vote + +votes -> admin --: add_admin { addr: new_admin } + +@enduml +``` + +I already put some hints about contracts implementation, but I will not go into them yet. + +## Messages forwarding + +There is one other thing we want to add - some way to give admins work. The `admin` contract would behave like +a proxy to call another contract. That means that some other external contract would just set our `admin` instance +as a specific address that can perform executions on it, and admins would perform actions this way. The external +contract would see execution as the admin contract would do it. Here is an updated contracts diagram: + +```plantuml +@startuml +class admin { + admins: Map + votings: Map + + propose_admin(candidate: Addr) + add_admin() + leave() + donate() + execute(contract: Addr, message: Binary) + + admins_list() -> Vec + join_time() -> Timestamp +} + +class voting { + votes: Vec + votes_needed: u64 + closed: bool + + accept() + votes_list() -> Vec +} + +class external {} + +admin o- voting: manages +admin -- external: forwards to +@enduml +``` + +And calling external contract flowchart: + +```plantuml +@startuml +actor Admin as admin +entity "Admin Contract" as contract +entity "External Contract" as external + +admin -> contract ++: exec execute { addr: "external_contract", msg: message_to_execute } +contract -> external ++: exec message_to_execute +deactivate external +deactivate contract +@enduml +``` + +Note that the `msg` on `execute` admin contract message is some arbitrary message just forwarded +to the external contract. It would be a base64-encoded message in the real world, but it is +just an implementation detail. + +Ultimately, we will create a simple example of an external contract to understand how to use such a pattern. diff --git a/src/cross-contract/fixing-admin.md b/src/cross-contract/fixing-admin.md new file mode 100644 index 0000000..d53b7dc --- /dev/null +++ b/src/cross-contract/fixing-admin.md @@ -0,0 +1,157 @@ +# Fixing admin contract + +Now that we know what we want to achieve, we can start by aligning the +contract we already have to become an admin contract. It is primarily +fine at this point, but we want to do a cleanup. + +## Cleaning up queries + +The first thing to do is to get rid of the `Greet` query - it was good as a +starter query example, but it has no practical purpose and only generates noise. + +We want to remove the unnecessary variant from the query enum: + +```rust +# use cosmwasm_schema::{cw_serde, QueryResponses}; +# use cosmwasm_std::Addr; +# +# #[cw_serde] +# pub struct InstantiateMsg { +# pub admins: Vec, +# pub donation_denom: String, +# } +# +# #[cw_serde] +# pub enum ExecuteMsg { +# Leave {}, +# Donate {}, +# } +# +# #[cw_serde] +# pub struct AdminsListResp { +# pub admins: Vec, +# } +# +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(AdminsListResp)] + AdminsList {}, +} +``` + +Then we also remove the invalid path in the query dispatcher: + +```rust +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + use QueryMsg::*; + + match msg { + AdminsList {} => to_binary(&query::admins_list(deps)?), + } +} +``` + +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`: + +```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 +only one contract, so we just worked on it as a whole project. Now we want some +directory tree that reflects relations between contracts we create. + +First, create a directory for the project. Then we want to create a "contracts" +subdirectory in it. It is not technically required from Rust's POV, but there +are tools in our environment, like the workspace optimizer, which would assume +it is where it should look for a contract. It is a common pattern you will see +in CosmWasm contracts repos. + +Then we copy the whole project directory from the previous chapter into the +`contracts`, renaming it to `admin`. + +Finally, we want to couple all our projects (for now, it is just one, but we know +there will be more there). To do so, we create the workspace-level `Cargo.toml` +file in the top-level project directory: + +```toml +[workspace] +members = ["contracts/*"] +resolver = "2" +``` + +This `Cargo.toml` differs slightly from the typical project-level one - it +defines the workspace. The most important field here is the `members` - it +defines projects being part of the workspace. + +The other field is the `resolver`. It is something to remember to add - it +instructs cargo to use version 2 of the dependency resolver. This has been the +default for non-workspaces since Rust 2021, but because of compatibility reasons, +the default couldn't be changed for workspaces - but it is advised to add it to +every single newly created workspace. + +The last field which might be useful for workspaces is exclude - it allows to +create projects in the workspace directory tree, which is not a part of this +workspace - we will not use it, but it is good to know about it. + +Now just for clarity, let's see the top-level directory structure: + +```none +. +├── Cargo.lock +├── Cargo.toml +├── contracts +│ └── admin +└── target + ├── CACHEDIR.TAG + └── debug +``` + +You can see the target directory and `Cargo.lock` files existing in the tree - it is +because I already built and ran tests for the `admin` contract - in Rust workspaces, +`cargo`` knows to build everything in the top level, even if it would be built from +the inner directory. diff --git a/src/cross-contract/map-storage.md b/src/cross-contract/map-storage.md new file mode 100644 index 0000000..eb62da6 --- /dev/null +++ b/src/cross-contract/map-storage.md @@ -0,0 +1,331 @@ +# Map storage + +There is one thing to be immediately improved in the admin contract. Let's +check the contract state: + +```rust +# use cosmwasm_std::Addr; +# use cw_storage_plus::Item; +# +pub const ADMINS: Item> = Item::new("admins"); +pub const DONATION_DENOM: Item = Item::new("donation_denom"); +``` + +Note that we keep our admin list as a single vector. However, in the whole +contract, in most cases, we access only a single element of this vector. + +This is not ideal, as now, whenever we want to access the single admin entry, +we have first to deserialize the list containing all of them and then iterate +over them until we find the interesting one. This might consume a serious +amount of gas and is completely unnecessary overhead - we can avoid that using +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. +We define it as similar to an `Item`, but this time we need two types - the key type +and the value type: + +```rust +use cw_storage_plus::Map; + +pub const STR_TO_INT_MAP: Map = Map::new("str_to_int_map"); +``` + +Then to store some items on the [`Map`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html), +we use a +[`save`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.save) +method - same as for an `Item`: + +```rust +STR_TO_INT_MAP.save(deps.storage, "ten".to_owned(), 10); +STR_TO_INT_MAP.save(deps.storage, "one".to_owned(), 1); +``` + +Accessing entries in the map is also as easy as reading an item: + +```rust +let ten = STR_TO_INT_MAP.load(deps.storage, "ten".to_owned())?; +assert_eq!(ten, 10); + +let two = STR_TO_INT_MAP.may_load(deps.storage, "two".to_owned())?; +assert_eq!(two, None); +``` + +Obviously, if the element is missing in the map, the +[`load`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.load) +function will result in an error - just like for an item. On the other hand - +[`may_load`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.may_load) +returns a `Some` variant when element exits. + +Another very useful accessor that is specific to the map is the +[`has`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.has) +function, which checks for the existence of the key in the map: + +```rust +let contains = STR_TO_INT_MAP.has(deps.storage, "three".to_owned())?; +assert!(!contains); +``` + +Finally, we can iterate over elements of the maps - either its keys or key-value +pairs: + +```rust +use cosmwasm_std::Order; + +for k in STR_TO_INT_MAP.keys(deps.storage, None, None, Order::Ascending) { + let _addr = deps.api.addr_validate(k?); +} + +for item in STR_TO_INT_MAP.range(deps.storage, None, None, Order::Ascending) { + let (_key, _value) = item?; +} +``` + +First, you might wonder about extra values passed to +[`keys`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.keys) +and +[`range`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.range) - +those are in order: lower and higher bounds of iterated elements, and the order +elements should be traversed. + +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 +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 +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 +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 +things! Or do they? + +Let's reconsider what we keep in the `ADMINS` vector - we have a list of objects +which we expect to be unique, which is a definition of a mathematical set. So +now let me bring back my initial definition of the map: + +> First, let's define a map - in this context, it would be a *set* of keys with +> values assigned to them, just like a HashMap in Rust or dictionaries in many languages. + +I purposely used the word "set" here - the map has the set built into it. It is +a generalization of a set or reversing the logic - the set is a particular case +of a map. If you imagine a set that map every single key to the same value, then +the values become irrelevant, and such a map becomes a set semantically. + +How can you make a map mapping all the keys to the same value? We pick a type +with a single value. Typically in Rust, it would be a unit type (`()`), but in +CosmWasm, we tend to use the +[`Empty`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.Empty.html) +type from CW standard crate: + +```rust +use cosmwasm_std::{Addr, Empty}; +use cw_storage_plus::Map; + +pub const ADMINS: Map = Map::new("admins"); +``` + +We now need to fix the usage of the map in our contract. Let's start with contract +instantiation: + +```rust +use crate::msg::InstantiateMsg; +use crate::state::{ADMINS, DONATION_DENOM}; +use cosmwasm_std::{ + DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; + +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + for addr in msg.admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, admin, &Empty {})?; + } + DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; + + Ok(Response::new()) +} +``` + +It didn't simplify much, but we no longer need to collect our address. Then +let's move to the leaving logic: + +```rust +use crate::state::ADMINS; +use cosmwasm_std::{DepsMut, MessageInfo}; + +pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { + ADMINS.remove(deps.storage, info.sender.clone()); + + let resp = Response::new() + .add_attribute("action", "leave") + .add_attribute("sender", info.sender.as_str()); + + Ok(resp) +} +``` + +Here we see a difference - we don't need to load a whole vector. We remove a +single entry with the +[`remove`](https://docs.rs/cw-storage-plus/1.0.1/cw_storage_plus/struct.Map.html#method.remove) +function. + +What I didn't emphasize before, and what is relevant, is that `Map` stores every +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 +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. + +Unfortunately, we have such an iteration in our contract - the distribution flow +becomes as follows: + +```rust +use crate::error::ContractError; +use crate::state::{ADMINS, DONATION_DENOM}; +use cosmwasm_std::{ + coins, BankMsg,DepsMut, MessageInfo, Order, Response +}; + +pub fn donate(deps: DepsMut, info: MessageInfo) -> Result { + let denom = DONATION_DENOM.load(deps.storage)?; + let admins: Result, _> = ADMINS + .keys(deps.storage, None, None, Order::Ascending) + .collect(); + let admins = admins?; + + let donation = cw_utils::must_pay(&info, &denom)?.u128(); + + let donation_per_admin = donation / (admins.len() as u128); + + let messages = admins.into_iter().map(|admin| BankMsg::Send { + to_address: admin.to_string(), + amount: coins(donation_per_admin, &denom), + }); + + let resp = Response::new() + .add_messages(messages) + .add_attribute("action", "donate") + .add_attribute("amount", donation.to_string()) + .add_attribute("per_admin", donation_per_admin.to_string()); + + Ok(resp) +} +``` + +If I had to write a contract like this, and this `donate` would be a critical, +often called flow, I would advocate for going for an `Item>` here. +Fortunately, it is not the case - the distribution does not have to be linear in +complexity! It might sound a bit crazy, as we have to iterate over all receivers +to distribute funds, but this is not true - there is a pretty nice way to do so +in constant time, which I will describe later in the book. For now, we will +leave it as it is, acknowledging the flaw of the contract, which we will fix later. + +The final function to fix is the `admins_list` query handler: + +```rust +use crate::state::ADMINS; +use cosmwasm_std::{Deps, Order, StdResult}; + +pub fn admins_list(deps: Deps) -> StdResult { + let admins: Result, _> = ADMINS + .keys(deps.storage, None, None, Order::Ascending) + .collect(); + let admins = admins?; + 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 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. + +## Reference keys + +There is one subtlety to improve in our map usage. + +The thing is that right now, we index the map with the owned Addr key. That forces +us to clone it if we want to reuse the key (particularly in the leave implementation). +This is not a huge cost, but we can avoid it - we can define the key of the map +to be a reference: + +```rust +use cosmwasm_std::{Addr, Empty}; +use cw_storage_plus::Map; + +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: + +```rust +# use crate::state::{ADMINS, DONATION_DENOM}; +# use cosmwasm_std::{ +# DepsMut, Empty, Env, MessageInfo, Response, StdResult, +# }; +# +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + for addr in msg.admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, &admin, &Empty {})?; + } + + // ... + +# DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; +# + Ok(Response::new()) +} + +pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult { + ADMINS.remove(deps.storage, &info.sender); + + // ... + +# let resp = Response::new() +# .add_attribute("action", "leave") +# .add_attribute("sender", info.sender.as_str()); +# + Ok(resp) +} +``` diff --git a/src/cross-contract/working-with-time.md b/src/cross-contract/working-with-time.md new file mode 100644 index 0000000..0457e83 --- /dev/null +++ b/src/cross-contract/working-with-time.md @@ -0,0 +1,156 @@ +# Working with time + +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 +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 time is encoded in the +[`Env`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.Env.html) +type in its +[`block`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.BlockInfo.html) +field, which looks like this: + +```rust +pub struct BlockInfo { + pub height: u64, + pub time: Timestamp, + pub chain_id: String, +} +``` + +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). + +Also, many transactions might be executed in a single block. +That means that if we need a unique id for the execution of +a particular message, we should look for something more. +This thing is a +[`transaction`](https://docs.rs/cosmwasm-std/1.2.4/cosmwasm_std/struct.TransactionInfo.html) +field of the `Env` type: + +```rust +pub struct TransactionInfo { + pub index: u32, +} +``` + +The `index` here contains a unique index of the transaction +in the block. That means that to get the unique identifier +of a transaction through the whole block, we can use the +`(height, transaction_index)` pair. + +## Join time + +We want to use the time in our system to keep track of the +join time of admins. We don't yet add new members to the +group, but we can already set the join time of initial +admins. Let's start updating our state: + +```rust +use cosmwasm_std::{Addr, Timestamp}; +use cw_storage_plus::Map; +# use cw_storage_plus::Item; + +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. + +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 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: + +```rust +use crate::state::{ADMINS, DONATION_DENOM}; +use cosmwasm_std::{ + DepsMut, Env, MessageInfo, Response, StdResult, +}; + +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + for addr in msg.admins { + let admin = deps.api.addr_validate(&addr)?; + ADMINS.save(deps.storage, &admin, &env.block.time)?; + } + DONATION_DENOM.save(deps.storage, &msg.donation_denom)?; + + Ok(Response::new()) +} +``` + +Instead of storing `&Empty {}` as an admin value, we store +the join time, which we read from `&env.block.time`. Also, +note that I removed the underscore from the name of the +`env` block - it was there only to ensure the Rust compiler +the variable is purposely unused and not some kind of a bug. + +Finally, remember to remove any obsolete `Empty` imports +through the project - the compiler should help you point out +unused imports. + +## Query and tests + +The last thing to add regarding join time is the new query +asking for the join time of a particular admin. Everything +you need to do that was already discussed, I'll leave it for +you as an exercise. The query variant should look like: + +```rust +#[returns(JoinTimeResp)] +JoinTime { admin: String }, +``` + +And the example response type: + +```rust +#[cw_serde] +pub struct JoinTimeResp { + pub joined: Timestamp, +} +``` + +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. diff --git a/src/introduction/overview-cosmwasm.md b/src/introduction/overview-cosmwasm.md deleted file mode 100644 index 744cb71..0000000 --- a/src/introduction/overview-cosmwasm.md +++ /dev/null @@ -1,27 +0,0 @@ -# Introduction to CosmWasm 2.0 - -Welcome to the comprehensive guide on CosmWasm 2.0. This book is designed to provide a deep understanding of the CosmWasm framework, a leading tool in the smart contract development space, especially in the context of the blockchain ecosystem. - -## Overview of CosmWasm - -CosmWasm is an advanced framework for building smart contracts in the Rust programming language. It is designed to operate within the Cosmos ecosystem but is versatile enough to be adapted for other blockchains. Key features include: - -- **Multi-Chain Compatibility**: Designed for interoperability across different blockchain networks. -- **Rust-Based Development**: Leverages Rust's safety features and performance for contract development. -- **Modular Design**: Flexible and modular, allowing for easy integration and upgrades. - -CosmWasm's architecture supports a wide range of applications, from simple tokens to complex decentralized finance (DeFi) protocols. With its emphasis on enhancing developer experiences and robust security, CosmWasm is a choice framework for building the next generation of decentralized applications. - -## CosmWasm API Documentation - -This document serves as a guide-like introduction to developing with CosmWasm, designed to provide you with a foundational understanding of smart contract development in this ecosystem. However, for those who are delving into the development process and require detailed reference materials, the API documentation is an invaluable resource. To further your knowledge and assist with specific development needs, you may find the following API documentation links particularly useful: - -- **cosmwasm-std:** The standard library for CosmWasm smart contracts, offering essential utilities and interfaces for interacting with the blockchain. Click [here](https://github.com/CosmWasm/cosmwasm) to learn more -- **cw-storage-plus:** A set of extensions to the basic storage capabilities, simplifying common patterns for storing and accessing data within smart contracts. Click [here](https://github.com/CosmWasm/cw-storage-plus) to learn more -- **cw-multi-test:** A testing framework designed to facilitate complex contract interactions and integration testing scenarios. Click [here](https://github.com/CosmWasm/cw-multi-test) to learn more -- cw-utils: Utility functions and helpers that provide additional functionality beyond the standard library. -- **sylvia framework:** A comprehensive framework for CosmWasm development, offering advanced features and tools for contract creation. Click [here](https://github.com/CosmWasm/sylvia) to learn more - -## Contributing to the Book - -This book is a collaborative effort, maintained and updated through contributions from the community. Hosted on GitHub, it is continuously improved through the insights, corrections, and contributions of developers and writers alike. If you encounter any issues, mistakes, or areas of ambiguity within this document, your contributions are welcome and appreciated. Please feel free to create an issue or submit a pull request on the GitHub repository. Your input helps ensure that this guide remains a valuable and accurate resource for the CosmWasm community. diff --git a/src/introduction/what's-new-2.0.md b/src/introduction/what's-new-2.0.md deleted file mode 100644 index 7215dc9..0000000 --- a/src/introduction/what's-new-2.0.md +++ /dev/null @@ -1,45 +0,0 @@ -# What's New in CosmWasm 2.0 - -The release of **CosmWasm 2.0** marks a significant milestone in the evolution of the CosmWasm framework. This version introduces an array of new features, performance enhancements, and security improvements, establishing a more robust, efficient, and secure foundation for decentralized application development. Key updates include advanced interoperability through IBC integration, optimized smart contract performance, and comprehensive security upgrades, ensuring that CosmWasm remains at the forefront of smart contract development. - -## From 1.5 to 2.0: A Leap Forward - -Transitioning from CosmWasm 1.5 to 2.0 represents a considerable advancement in the framework's development. This update addresses the evolving requirements of the developer community and adapts to the ever-changin landscape of blockchain technology. - -## Changelog Highlights - -For a detailed list of all changes, improvements, and bug fixes in CosmWasm 2.0, we encourage you to review the official changelog on [GitHub](https://github.com/CosmWasm/cosmwasm/blob/main/CHANGELOG.md). The changelog provides an exhaustive breakdown of the updates, offering insights into the development focus areas and enhancements that have been prioritized in this release. - -We invite you to review the [official changelog on GitHub](https://github.com/CosmWasm/cosmwasm/blob/main/CHANGELOG.md) for a detailed account of all changes, enhancements, and fixes introduced in CosmWasm 2.0. This document offers a thorough overview of the development priorities and the significant updates made in this release. - - -## Highlights from the changelog include: - -- **IBC Integration**: The addition of IbcMsg::Transfer with an optional memo field enhances the IBC functionality, allowing for richer cross-chain interactions. The introduction of IbcReceiveResponse::without_ack constructor and making acknowledgement optional streamline the handling of IBC packets, facilitating a more efficient integration process with other IBC-enabled blockchains. ([#1878], [#1892]) -- **Performance Optimizations**: The resolution of memory increase issues and the optimization of Wasmer Engine usage significantly boost contract execution efficiency. The upgrade to Wasmer 4.2.5, alongside adjustments to memory handling and function parameter limits, contributes to overall performance improvements, ensuring faster execution and lower gas costs. ([#1978], [#1992], [#2005], [#1991]) -- **Security Enhancements**: Fixes addressing specific vulnerabilities, such as CWA-2023-004, directly contribute to the security robustness of CosmWasm contracts. The removal of features that potentially impact security, coupled with the continuous update and refinement of the contract environment, ensures a safer execution space for smart contracts. ([#1996]) -- **Developer Tooling** : The update focused on streamlining error handling and backtraces within smart contracts. The backtraces feature has been removed from cosmwasm-std, and developers can now utilize the RUST_BACKTRACE=1 environment variable for error diagnostics. This change ensures that error variants consistently contain backtrace information, simplifying debugging processes for developers. ([#1967]) - -These updates are part of our ongoing commitment to providing a powerful, flexible, and secure smart contract platform that meets the evolving needs of the blockchain developer community. For more information and a complete list of changes, please visit the [CosmWasm 2.0 changelog](https://github.com/CosmWasm/cosmwasm/blob/main/CHANGELOG.md). - -## Impact on the Developer and User Community - -The advancements introduced in CosmWasm 2.0 are set to profoundly influence the ecosystem, benefiting both developers and users by expanding the horizons of decentralized application development: - -- **### For Developers**: - -- **Increased Power and Flexibility**: With advanced features like IBC integration, developers can now create truly interoperable dApps that communicate across different blockchains, opening up a world of possibilities for cross-chain applications. -- **Streamlined Development Process**: Enhanced developer tooling and debugging capabilities make it easier to build, test, and deploy contracts, reducing development time and allowing for a greater focus on innovation. -- **Enhanced Performance**: Optimizations in contract execution speed and efficiency not only lower operational costs but also enable more complex functionalities to be implemented without compromising on performance. - -- **### For Users**: - -- **Improved Application Performance**: Users will experience faster transaction processing times and reduced gas fees, thanks to the performance improvements in CosmWasm 2.0. This leads to a smoother and more cost-effective user experience. -- **Greater Security and Reliability**: Security enhancements and the introduction of new protocols mean that users can interact with applications with increased confidence in their safety and integrity. -- **Access to a Wider Range of Applications**: The interoperability features unlocked by IBC integration enable the development of new types of applications that leverage the strengths of multiple blockchains, providing users with access to more diverse and powerful services. - -## Embark on the Journey - -As we explore these updates in subsequent chapters, we will offer detailed insights, practical guides on leveraging the new features, and showcase the transformative impact of CosmWasm 2.0 on smart contract development. This journey through CosmWasm 2.0 is designed to equip both seasoned developers and newcomers with the knowledge and skills needed to excel in the blockchain technology domain. - - diff --git a/src/part1/section-1-getting-started-w/1.1.basic-concept.md b/src/part1/section-1-getting-started-w/1.1.basic-concept.md deleted file mode 100644 index fdedf36..0000000 --- a/src/part1/section-1-getting-started-w/1.1.basic-concept.md +++ /dev/null @@ -1,173 +0,0 @@ -# 2. Basic Concepts of CosmWasm - -## Understanding Smart Contracts - -In the evolving landscape of blockchain technology, smart contracts emerge as a cornerstone, automating the execution of agreements without the need for intermediaries. These digital contracts store, verify, and enforce the negotiation or performance of an agreement or transaction. This section delves into the intrinsic properties of smart contracts and elucidates the significant enhancements introduced by CosmWasm, a pioneering framework that amplifies their potential within the blockchain ecosystem. - -## The Quintessence of Smart Contracts: Self-Executing and Immutable - -Smart contracts are fundamentally characterized by their self-executing and immutable nature. They are programs that automatically execute the terms of a contract upon the fulfillment of specified conditions. This automation not only streamlines processes but also instills a level of trust and reliability previously unattainable in digital agreements. Once deployed on a blockchain, these contracts are immutable, a trait ensuring that their logic cannot be altered or tampered with, further cementing their role as a trust mechanism in digital transactions. - -## CosmWasm's Contributions to Smart Contract Evolution - -CosmWasm extends the functionality of smart contracts through three pivotal enhancements: interoperability, developer accessibility, and scalability. - -- **Interoperability**: CosmWasm's architecture is ingeniously designed to facilitate smart contracts' deployment across diverse blockchain platforms within the Cosmos ecosystem. This cross-chain compatibility heralds a new era of decentralized applications (dApps) that leverage the unique strengths of multiple blockchains, fostering a more integrated and versatile blockchain landscape. - -- **Developer Accessibility**: By harnessing the power and safety of Rust, CosmWasm lowers the barrier to entry for smart contract development. Rust's stringent compile-time checks and emphasis on memory safety drastically reduce common security vulnerabilities, making the development process both more secure and accessible. - -- **Scalability**: Addressing one of the most pressing challenges in blockchain development, CosmWasm ensures that smart contracts can effectively scale to accommodate increasing demand. This is achieved through optimized performance and efficient state management, ensuring that dApps built on CosmWasm can serve expansive user bases without compromising on speed or functionality. - -## Synthesis: The Impact of CosmWasm on Smart Contracts - -Through its innovative enhancements, CosmWasm redefines the possibilities of smart contracts, making them more adaptable, secure, and user-friendly. By addressing key challenges such as interoperability, security, and scalability, CosmWasm not only broadens the applicability of smart contracts but also paves the way for their increased adoption in solving complex real-world problems. As we venture further into the CosmWasm ecosystem, the profound impact of these advancements on the development of decentralized applications becomes increasingly evident, marking a significant evolution in the blockchain domain. - -### Enhancements by CosmWasm - -CosmWasm enhances traditional smart contract capabilities, focusing on: - -- **Compatibility**: Facilitates deployment across various blockchain platforms. -- **Scalability**: Ensures contracts can handle increasing workloads effectively. - -```rust -// Example Rust Code for CosmWasm's Compatibility -const CONFIG: Item = Item::new("config"); - -#[entry_point] -pub fn instantiate(deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg) -> StdResult { - let config = Config { - // we store the denomination we want to handle in the contract, e.g. "ujuno", "ukuji", etc. - // this allows us to upload the same contract to multiple chains and handle their native - // denominations by instantiating it with the correct one - denom: msg.denom, - }; -} - - -#[entry_point] -pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // we want exactly one denomination to be sent to this contract - if info.funds.len() != 1 { - return Err(ContractError::NeedExactlyOneAsset {})?; - } - let coin = &info.funds[0]; - let config = CONFIG.load(deps.storage)?; - - // we only accept the configured denomination - if coin.denom != config.denom { - return Err(ContractError::InvalidDenom { - expected: config.denom, - received: coin.denom, - })?; - } - - // Execute logic, e.g., swapping the coin for another asset and sending that back -} -``` - -```mermaid -sequenceDiagram - participant User as User - participant Instantiate as Instantiate Contract - participant Execute as Execute Contract - participant Storage as Contract Storage - participant Blockchain as Blockchain - - User->>+Instantiate: InstantiateMsg with denom - Note over Instantiate: Store denom in contract config - Instantiate->>+Storage: Save denom to CONFIG - Storage-->>-Instantiate: Config saved - Note over User,Execute: Later, user sends funds - User->>+Execute: ExecuteMsg with funds - Execute->>+Storage: Load CONFIG - Storage-->>-Execute: Return denom from CONFIG - Execute->>Execute: Verify funds' denom matches CONFIG - alt funds' denom matches - Execute->>Blockchain: Swap asset/send back - Blockchain-->>User: Transaction successful - else funds' denom does not match - Execute-->>User: Error: InvalidDenom - end -``` -## CosmWasm Architecture Overview - -CosmWasm's architecture is built on several key components that work together to provide a seamless environment for deploying and executing smart contracts on various blockchain platforms. These components include: - -- **Wasm Virtual Machine (VM):** At the core of CosmWasm is a WebAssembly (Wasm) VM that allows smart contracts to be written in Rust (or other supported languages) and compiled to Wasm bytecode. This VM provides an execution environment that is both secure and isolated from the blockchain's native execution environment. - -- **CosmWasm Runtime:** This is a layer that interfaces between the blockchain (such as Cosmos SDK-based chains) and the Wasm VM. It handles the instantiation of contracts, dispatches messages to them, and manages state persistence. The runtime also deals with contract upgrades, migrations, and query handling. - -- **Smart Contracts:** These are self-contained programs written by developers to implement business logic. They are deployed on the blockchain and executed within the Wasm VM. Contracts can communicate with each other and with the blockchain through the CosmWasm runtime. - -- **Blockchain Interface:**CosmWasm abstracts blockchain-specific functionalities through a set of interfaces. These interfaces allow the CosmWasm runtime to interact with blockchain features like token transfers, account management, and consensus mechanisms without being tied to a specific implementation. - -- **IBC Module:** For blockchains built with the Cosmos SDK, CosmWasm integrates with the Inter-Blockchain Communication (IBC) protocol to enable cross-chain interactions. The IBC module in CosmWasm facilitates the sending and receiving of messages and assets across different blockchain networks. - -- **CosmJS:** This is a JavaScript library that developers can use to interact with CosmWasm contracts from client-side applications. It provides functionalities for querying contract states, executing contract functions, and listening to contract events. - -Below is a Mermaid diagram that visualizes the CosmWasm architecture: - -```mermaid -graph TD; - Blockchain[Blockchain Platform] -->|Interacts| Runtime[CosmWasm Runtime]; - Runtime -->|Executes| VM[Wasm Virtual Machine]; - VM -->|Runs| Contracts[Smart Contracts]; - Contracts -->|Communicate| IBC[IBC Module]; - Blockchain -->|Enables| IBC; - Runtime -->|Uses| Interface[Blockchain Interface]; - Interface -->|Abstracts| Blockchain; - Client[Client Applications] -->|Utilizes| CosmJS[CosmJS]; - CosmJS -->|Interacts with| Contracts; -``` -This diagram highlights the modular design of CosmWasm, showcasing how each component plays a crucial role in the ecosystem. The separation between the blockchain platform and the Wasm VM through the CosmWasm runtime ensures flexibility and compatibility across different blockchains. The inclusion of the IBC module underscores CosmWasm's commitment to interoperability, a key aspect of modern blockchain architectures. - -In summary, CosmWasm's architecture is thoughtfully designed to foster a secure, interoperable, and developer-friendly environment for smart contract development across the blockchain spectrum. Its modular components and integration with Cosmos SDK and IBC protocol make it a powerful tool for building the next generation of decentralized applications. - -## Key Components - -Contract Lifecycle: Manages deployment, execution, and termination of contracts. -Query and Execute Functions: Facilitate reading data and making state changes in the blockchain. - -```rust -// Example Rust Code for Query and Execute Functions -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - // Query logic, e.g., fetching contract state -} - -#[entry_point] -pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // Execute logic, e.g., updating state, sending funds -} -``` - -Communication Flow: Ensures effective communication within and between contracts in the CosmWasm ecosystem. - -```rust -// Example Rust Code for Communication in CosmWasm -#[entry_point] -pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - let res = Response::new() - // Adding attributes to the response, which can be seen as 'events' - .add_attribute("action", "execute") - .add_attribute("sender", info.sender.to_string()); - // Additional logic for inter-contract communication here - Ok(res) -} -``` - -```mermaid -sequenceDiagram - participant C1 as Contract 1 - participant C2 as Contract 2 - participant B as Blockchain - - C1->>+B: Send Message - B->>+C2: Route Message - Note over C2: Perform Logic - C2->>-B: Response - B-->>-C1: Acknowledgement -``` -Effective communication in the CosmWasm ecosystem is crucial for the seamless operation and interoperability of decentralized applications. This section delves into the significance of communication between contracts and the blockchain, highlighting the pivotal role of the `Response` object in signaling execution results and facilitating event-driven interactions. By ensuring a robust communication framework, CosmWasm enhances the functionality and reliability of smart contracts, making them more adaptable and efficient in the ever-evolving landscape of blockchain technology. - -CosmWasm's architecture, focusing on interoperability, security, and ease of use, makes it an ideal framework for building robust and scalable decentralized applications. diff --git a/src/part1/section-1-getting-started-w/1.2.setting-up-enviroment.md b/src/part1/section-1-getting-started-w/1.2.setting-up-enviroment.md deleted file mode 100644 index 7fb3681..0000000 --- a/src/part1/section-1-getting-started-w/1.2.setting-up-enviroment.md +++ /dev/null @@ -1,66 +0,0 @@ -# Setting up the Environment for CosmWasm 2.0 - -Setting up the right environment is crucial for developing with CosmWasm 2.0. This section covers all the necessary steps and tools required to get started. - -## Rust Installation - -CosmWasm 2.0 development requires Rust. If it's not installed on your machine, follow the instructions on [the Rust website](insert link here). - -```bash -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rust-lang.org | sh -``` - -# Wasm Compiler Backend - -You'll need the Wasm Rust compiler backend to build Wasm binaries for CosmWasm. Install it with the following command: -``` -rustup target add wasm32-unknown-unknown - -``` - -# Testing with `wasmd` - -For testing contracts on a testnet, the wasmd binary is required. Though this book primarily focuses on Rust unit testing, deploying contracts to a testnet can be beneficial. -```rust -// Install `wasmd` -git clone https://github.com/CosmWasm/wasmd.git -cd wasmd -make install -``` - -# Docker Installation - -Docker is needed for uploading Rust Wasm contracts to the blockchain and for running the CosmWasm Rust Optimizer. This is essential to ensure your contracts do not exceed size limits. - -# Install Docker -# Visit [Docker's official website](insert link here) for specific installation instructions - -# Cosmwasm-Check Utility - -The cosmwasm-check utility helps verify if your wasm binary is ready for blockchain deployment. Install it using cargo: -```rust -cargo install cosmwasm-check -``` -To check if the installation was successful, run: -```rust -cosmwasm-check --version -``` -# Verifying the Installation - -To ensure your environment is correctly set up for CosmWasm 2.0 development, build example contracts from the cw-plus repository: -```rust -git clone https://github.com/CosmWasm/cw-plus.git -cd cw-plus -cargo test -``` -The cw-plus repository, maintained by CosmWasm creators, is an excellent source for example contracts. - -To verify the cosmwasm-check utility, build a contract and check its validity: - -```rust -cd contracts/cw1-whitelist -cargo wasm -cosmwasm-check ../../target/wasm32-unknown-unknown/release/cw1_whitelist.wasm -``` -This process will ensure that you are fully equipped and ready to start developing with CosmWasm 2.0, taking advantage of all its new features and improvements. diff --git a/src/part1/section-1-getting-started-w/1.3.entry-points.md b/src/part1/section-1-getting-started-w/1.3.entry-points.md deleted file mode 100644 index 0a0eb01..0000000 --- a/src/part1/section-1-getting-started-w/1.3.entry-points.md +++ /dev/null @@ -1,46 +0,0 @@ -# Entry Points -Unlike traditional Rust applications that start with a `main()` function, smart contracts have several entry points corresponding to different message types, such as instantiate, execute, and query. These entry points allow the contract to interact with the blockchain in various ways. - -To define these entry points, use the #[entry_point] macro from CosmWasm. Here's an example for the instantiate and query entry points: -```rust -use cosmwasm_std::{entry_point, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Binary, Empty}; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Empty, -) -> StdResult { - // Initialization logic here - Ok(Response::new()) -} - -#[entry_point] -pub fn query( - deps: Deps, - env: Env, - msg: Empty, -) -> StdResult { - // Query logic here - Ok(Binary::from("Hello, World!")) -} -``` - -```mermaid -sequenceDiagram - participant User as User/External Contract - participant Instantiate as Instantiate Entry Point - participant Query as Query Entry Point - participant Response as Response (Instantiate) - participant Binary as Binary (Query) - - User->>Instantiate: Calls with MessageInfo, Env, Empty - Note over Instantiate: Initialization Logic - Instantiate->>Response: Returns StdResult - - User->>Query: Calls with Deps, Env, Empty - Note over Query: Query Logic - Query->>Binary: Returns StdResult -``` -This diagram starts with the user or an external contract making a call to either the `instantiate` or `query` entry points, providing the necessary data (`MessageInfo`, `Env`, `Empty`). The `instantiate` function processes the initialization logic and returns a `StdResult`, indicating the outcome of the initialization. Similarly, the query function processes the incoming `query` and returns a `StdResult`, encapsulating the query's response data. \ No newline at end of file diff --git a/src/part1/section-1-getting-started-w/1.4.first-steps-write-smartcontract.md b/src/part1/section-1-getting-started-w/1.4.first-steps-write-smartcontract.md deleted file mode 100644 index 6414d1d..0000000 --- a/src/part1/section-1-getting-started-w/1.4.first-steps-write-smartcontract.md +++ /dev/null @@ -1,109 +0,0 @@ -# 3. First Steps: Writing a Simple Contract - -## Building Your First Contract - -### Tutorial on Basic Contract Structure - -CosmWasm smart contracts are written in Rust and follow a specific structure. Key components include: - -- **Instantiate Function**: Initializes the contract state. -- **Execute Function**: Contains the logic that alters the contract state. -- **Query Function**: Used for reading contract state without making changes. - -```rust -// Example of a simple CosmWasm contract structure -use cosmwasm_std::{ - ensure_eq, entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, - Response, StdResult, -}; -use cw_storage_plus::Item; - -const OWNER: Item = Item::new("owner"); - -#[entry_point] -pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: InstantiateMsg) -> StdResult { - // Set initial state (owner in this case) - // In most cases, you will also want to save some data from the InstantiateMsg - OWNER.save(deps.storage, &info.sender)?; - - Ok(Response::default()) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result { - match msg { - ExecuteMsg::SetOwner { owner } => { - // validate that the sender is the current owner - let current_owner = OWNER.load(deps.storage)?; - ensure_eq!(info.sender, current_owner, ContractError::Unauthorized {}); - // validate the new owner address - let owner = deps.api.addr_validate(&owner)?; - // Set the owner - OWNER.save(deps.storage, &owner)?; - - Ok(Response::default()) - } - // ... other execute message handlers - } -} - -#[entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Owner {} => to_json_binary(&OWNER.load(deps.storage)?), - // ... other query message handlers - } -} -``` - -## Tips for Writing Clean and Efficient Code - -Follow Rust Best Practices: Utilize Rust's features like ownership, types, and error handling to write safe and efficient code. - -- Keep it Simple: Start with a simple logic and gradually add complexity. -- Testing: Regularly test your code to catch and fix errors early. - -## Deploying and Testing the Contract - -### Compilation and Deployment - -Compile your contract to WebAssembly (WASM) and deploy it either locally or on a testnet. - -# Compile the contract to - -``` -cargo wasm - -# Deploy using CosmWasm tooling (specific commands vary based on the deployment target) -``` - -# Introduction to Testing Frameworks - -CosmWasm provides testing frameworks that allow you to write unit tests for your contracts. - -```rust -// Example of a simple unit test in CosmWasm -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::{from_json, testing::*}; - - #[test] - fn instantiate_sets_owner() { - let mut deps = mock_dependencies(); - // create a valid address from an arbitrary string - let owner = deps.api.addr_make("owner"); - let info = mock_info(owner.as_str(), &[]); - - let res = instantiate(deps.as_mut(), mock_env(), info, InstantiateMsg {}).unwrap(); - assert_eq!(0, res.messages.len()); - - // query the owner - let queried_owner = query(deps.as_ref(), mock_env(), QueryMsg::Owner {}).unwrap(); - let queried_owner: Addr = from_json(&queried_owner).unwrap(); - - // ensure it was properly stored - assert_eq!(owner, queried_owner); - } -} -``` \ No newline at end of file diff --git a/src/part1/section-2-interacting-w/1.5.creating-query.md b/src/part1/section-2-interacting-w/1.5.creating-query.md deleted file mode 100644 index d223ba8..0000000 --- a/src/part1/section-2-interacting-w/1.5.creating-query.md +++ /dev/null @@ -1,56 +0,0 @@ -## Creating a Custom Query -After setting up the basic structure and understanding entry points, the next step is to enrich the smart contract with the ability to respond to specific queries. This requires defining a query message type and corresponding handler within the contract. - -## Define the Query Message Type -First, define the types of queries your contract should respond to. This involves creating enums or structs that represent different query requests. Here's an example using `serde` for serialization: -```rust -use serde::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum QueryMsg { - // Fetches a greeting message - GetGreeting {}, -} -``` -## Implement the Query Handler -Next, implement the query handler to process incoming query messages and return the appropriate response. The response must be serialized into a `Binary` format, as per the CosmWasm standard. -```rust -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetGreeting {} => to_json_binary(&"Hello, CosmWasm!"), - } -} -``` -This simple handler matches on the `QueryMsg` type and returns a static greeting message. For more complex queries, you might access and manipulate the contract's state using `deps.storage`. - -# Creating a Custom Query in CosmWasm - -Creating a custom query in CosmWasm allows smart contracts to respond to specific inquiries, enhancing the contract's interaction capabilities. This section covers the step-by-step process of defining and implementing custom queries within your CosmWasm contract. - -## Define the Query Message Type - -The first step is defining the types of queries your contract should respond to. This involves creating enums or structs that encapsulate different query requests. -```rust -use serde::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum QueryMsg { - // Fetches a greeting message - GetGreeting {}, -} -``` -## Implement the Query Handler - -Next, implement a handler function to process incoming query messages and return the appropriate response. The response must be serialized into a Binary format. -```rust -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetGreeting {} => to_binary(&"Hello, CosmWasm!"), - } -} -``` -## Code Example: Custom Query Implementation - -Here, we match on the QueryMsg type and return a greeting message. This example demonstrates handling a simple query. For more complex queries, accessing and manipulating the contract's state using deps.storage might be necessary \ No newline at end of file diff --git a/src/part1/section-2-interacting-w/1.6.testing-query.md b/src/part1/section-2-interacting-w/1.6.testing-query.md deleted file mode 100644 index 68a6be6..0000000 --- a/src/part1/section-2-interacting-w/1.6.testing-query.md +++ /dev/null @@ -1,77 +0,0 @@ -## Testing a Query -Testing in CosmWasm is facilitated by the `cosmwasm_std::testing` module, which provides utilities to mock dependencies, environmental conditions, and messages. Testing your queries is crucial to ensure they return the expected responses under various conditions. - -## Setting Up Tests -To write tests, create a module within your contract file or in a separate file under a tests directory. Use the `#[cfg(test)]` attribute to denote test modules and `#[test]` for individual test functions. - -```rust -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cosmwasm_std::{from_binary, to_binary}; - - #[test] - fn query_greeting() { - let deps = mock_dependencies(&[]); - let env = mock_env(); - let msg = QueryMsg::GetGreeting {}; - - // Perform the query - let response = query(deps.as_ref(), env, msg).unwrap(); - let greeting: String = from_binary(&response).unwrap(); - - // Assert the expected result - assert_eq!("Hello, CosmWasm!", greeting); - } -} -``` -This example demonstrates a simple test for our `GetGreeting` query. It uses `mock_dependencies` to create a mock environment and `mock_env` for the environmental data. The test calls the `query` function with a `GetGreeting` message and asserts that the response matches the expected greeting message. - -## Advanced Testing Strategies -For more complex queries that involve reading from or writing to the contract's state, you might: - -- Initialize the contract's state within the test setup. -- Use `mock_info` to simulate messages from specific addresses or with attached funds. -- Test various edge cases and error conditions to ensure the contract behaves correctly under all circumstances. - -Testing is a critical part of smart contract development, ensuring the reliability and security of your contract's functionality. - -# Testing a Query in CosmWasm - -Testing queries in CosmWasm is essential for verifying that your smart contract behaves as expected. This section provides a guide to setting up tests for your contract's query functions. - -# Setting Up Tests - -To begin testing, create a module within your contract file or in a separate directory. Use the #[cfg(test)] attribute to specify test modules and #[test] for individual test functions. -```rust -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cosmwasm_std::{from_json, to_json_binary}; - - #[test] - fn query_greeting() { - let deps = mock_dependencies(&[]); - let env = mock_env(); - let msg = QueryMsg::GetGreeting {}; - - let response = query(deps.as_ref(), env, msg).unwrap(); - let greeting: String = from_json(&response).unwrap(); - - assert_eq!("Hello, CosmWasm!", greeting); - } -} -``` -### Advanced Testing Strategies - -For more complex queries, consider: - -- Initializing the contract's state within the test setup. -- Using `mock_info` to simulate messages from specific addresses. -- Testing edge cases and error conditions. - -## Conclusion - -Testing is a crucial aspect of smart contract development in CosmWasm, ensuring the functionality and reliability of your contract's queries. \ No newline at end of file diff --git a/src/part1/section-2-interacting-w/1.7.introducing-multi-test.md b/src/part1/section-2-interacting-w/1.7.introducing-multi-test.md deleted file mode 100644 index bbe28de..0000000 --- a/src/part1/section-2-interacting-w/1.7.introducing-multi-test.md +++ /dev/null @@ -1,56 +0,0 @@ -# Multi-test examples - -## Leveraging cw-multi-test for Contract Testing -In the CosmWasm ecosystem, rigorous testing of smart contracts is essential to ensure their correctness and reliability. The `cw-multi-test` library offers a powerful toolset for simulating a blockchain environment, allowing developers to test both individual contracts and interactions between multiple contracts efficiently. - -## Getting Started with cw-multi-test -To incorporate cw-multi-test into your project for development and testing purposes, you must add it to your Cargo.toml under [dev-dependencies]. This ensures that cw-multi-test is utilized during the development phase without being included in your production builds. Update your Cargo.toml file as follows: -```rust -[dev-dependencies] -cw-multi-test = "0.13.4" # Ensure this version matches the latest release - -``` -Creating Your First Test with cw-multi-test -cw-multi-test simplifies the process of writing tests by providing a virtual blockchain environment where contracts can be deployed, interacted with, and observed. Here's an example to guide you through writing a basic test: -```rust -#[cfg(test)] -mod tests { - use cosmwasm_std::{Addr, Empty}; - use cw_multi_test::{App, Contract, ContractWrapper, Executor}; - use super::*; - - #[test] - fn basic_contract_interaction() { - // Initialize the App to simulate the blockchain - let mut app = App::default(); - - // Create a wrapper for your contract's functionalities - let contract = ContractWrapper::new(execute, instantiate, query); - - // Store and retrieve the contract's code ID within the app - let code_id = app.store_code(Box::new(contract)); - - // Simulate contract instantiation on the blockchain - let contract_addr = app - .instantiate_contract( - code_id, - Addr::unchecked("contract_owner"), - &Empty {}, - &[], - "My CosmWasm Contract", - None, - ) - .unwrap(); - - // Example of querying the instantiated contract - let query_response: MyQueryResponse = app - .wrap() - .query_wasm_smart(contract_addr, &MyQueryMsg {}) - .unwrap(); - - // Assertions can be made here based on the expected query response - assert_eq!(query_response, expected_response); - } -} -``` -This example demonstrates how to initialize the `cw-multi-test` app, wrap your contract functions, simulate storing the contract on the blockchain, instantiate the contract, and perform a query. Testing with `cw-multi-test` enables thorough interaction testing within a simulated environment, providing confidence in contract behavior before deployment. \ No newline at end of file diff --git a/src/part1/section-3-advanced/1.10.-events-attributes-data.md b/src/part1/section-3-advanced/1.10.-events-attributes-data.md deleted file mode 100644 index 360a657..0000000 --- a/src/part1/section-3-advanced/1.10.-events-attributes-data.md +++ /dev/null @@ -1,79 +0,0 @@ -In the evolving landscape of CosmWasm 2.0, the mechanisms for smart contracts to communicate with the external world have been refined and expanded. This includes the use of events for logging activities and data for structured information exchange between contracts. Here, we delve into how to leverage these tools in the CosmWasm 2.0 framework, particularly focusing on the addition of administrators as a practical example of event usage. - -# Events: Broadcasting Activities -Events are integral to smart contracts in CosmWasm 2.0, providing a way for contracts to emit information about activities or changes that occur during their execution. This is particularly useful for tracking actions, such as the addition of new administrators, through the blockchain's event logs. - -# Example: Emitting an admin_added Event -Consider a scenario where your contract emits an admin_added event upon successfully adding a new administrator through the AddMembers function: -```rust -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, -}; - -pub fn add_members( - deps: DepsMut, - info: MessageInfo, - admins: Vec, -) -> Result { - let mut curr_admins = ADMINS.load(deps.storage)?; - if !curr_admins.contains(&info.sender) { - 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()); - - let admins: StdResult> = admins.into_iter().map(|addr| deps.api.addr_validate(&addr)).collect(); - curr_admins.append(&mut admins?); - ADMINS.save(deps.storage, &curr_admins)?; - - Ok(resp) -} -``` -In this implementation, an event for each added admin is created and appended to the contract's response. This approach not only logs the addition of new administrators but also provides detailed information about the action, including the number of admins added. - -## Testing Event Emissions - -Testing for event emissions can sometimes introduce complexity due to the reliance on string-based keys and values. However, it's essential for ensuring that your contract behaves as expected. Here's how you might test the add_members function to verify that events are emitted correctly: - -```rust - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{from_binary, Addr, StdResult}; - - #[test] - fn add_members_emits_events() { - let mut deps = mock_dependencies(); - let info = mock_info("owner", &[]); - let add_members_msg = ExecuteMsg::AddMembers { admins: vec!["user".to_owned()] }; - - let response = add_members(deps.as_mut(), info, add_members_msg).unwrap(); - assert_eq!(response.events.len(), 1); // Check for one "admin_added" event - - let admin_added_event = &response.events[0]; - assert_eq!(admin_added_event.ty, "admin_added"); - assert_eq!(admin_added_event.attributes[0], ("addr", "user")); - } -} -``` -This test ensures that the add_members function emits the admin_added event with the correct attributes. - -## Data: Structured Contract-to-Contract Communication -While events offer a way to log activities, CosmWasm 2.0 also provides a mechanism for contracts to communicate structured data through responses. This is particularly useful for contract-to-contract interactions where a contract execution needs to return more complex information. - -Data is set in a contract's response using the set_data function and can include any binary-encoded information, allowing for flexible and efficient data exchange between contracts. - -## Integrating Events and Data into Your Contract -Understanding and implementing events and data in CosmWasm 2.0 are crucial for building advanced and interactive smart contracts. While events provide transparency and traceability for contract activities, data enables rich, structured communication between contracts, opening up a wide array of possibilities for decentralized application development. - -Incorporating these concepts into your CosmWasm 2.0 contracts not only enhances their functionality but also aligns with best practices for smart contract development in the Cosmos ecosystem. - - - - diff --git a/src/part1/section-3-advanced/1.11.-dealing-w-funds.md b/src/part1/section-3-advanced/1.11.-dealing-w-funds.md deleted file mode 100644 index efda77e..0000000 --- a/src/part1/section-3-advanced/1.11.-dealing-w-funds.md +++ /dev/null @@ -1,107 +0,0 @@ -# Dealing with Funds in CosmWasm 2.0 - -In the blockchain realm, particularly within the CosmWasm ecosystem, the handling of native tokens and the execution of financial transactions are fundamental capabilities of smart contracts. CosmWasm 2.0 enhances these capabilities, providing developers with a refined toolkit for managing cryptocurrencies—often referred to as tokens—directly within smart contracts. This section of the book explores the intricacies of dealing with funds in CosmWasm 2.0, focusing on a practical example: a donation system that rewards contract administrators. - -## Native Tokens and Smart Contracts -Native tokens are digital assets managed by the blockchain's core rather than by individual smart contracts. These tokens can have various roles, from facilitating transaction fees (gas) to acting as stakes in consensus mechanisms. In CosmWasm, smart contracts can own, send, and receive native tokens, enabling a wide range of financial operations. - -## Implementing a Donation System -To demonstrate the handling of funds in CosmWasm 2.0, let's implement a Donate function within a smart contract. This function allows users to donate tokens to the contract, which are then equally distributed among the contract's administrators. - -- **Step 1:** Defining Messages -First, we need to define the necessary messages for our contract. This includes a new variant in the ExecuteMsg enum for the Donate action and an adjustment to the InstantiateMsg to specify the token denomination for donations. - -```rust -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - pub admins: Vec, - pub donation_denom: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub enum ExecuteMsg { - AddMembers { admins: Vec }, - Leave {}, - Donate {}, -} -``` - -- **Step 2:** Managing State -We introduce a new state variable to store the donation denomination, utilizing the cw-storage-plus package for efficient state management. - -```rust -use cw_storage_plus::Item; - -pub const ADMINS: Item> = Item::new("admins"); -pub const DONATION_DENOM: Item = Item::new("donation_denom"); -``` - -- **Step 3:** Handling Donations - -The Donate execution logic involves accepting donations and equally distributing them among the administrators. This process requires validating the donated funds, calculating the share per administrator, and performing token transfers. - -```rust -use cosmwasm_std::{coins, BankMsg, DepsMut, MessageInfo, Response, StdResult, Addr, Coin}; - -fn donate(deps: DepsMut, info: MessageInfo) -> StdResult { - let denom = DONATION_DENOM.load(deps.storage)?; - let admins = ADMINS.load(deps.storage)?; - - let total_donation: Coin = info - .funds - .iter() - .find(|&coin| coin.denom == denom) - .ok_or_else(|| StdError::generic_err("No donations in the specified denom"))? - .clone(); - - let donation_per_admin = Coin { - denom: denom.clone(), - amount: total_donation.amount.u128() / (admins.len() as u128), - }; - - let messages: Vec<_> = admins.iter().map(|admin| BankMsg::Send { - to_address: admin.to_string(), - amount: vec![donation_per_admin.clone()], - }).collect(); - - Ok(Response::new() - .add_messages(messages) - .add_attribute("action", "donate") - .add_attribute("donation_denom", &denom) - .add_attribute("total_donation", total_donation.amount) - .add_attribute("donation_per_admin", donation_per_admin.amount)) -} -``` -- **Step 4:** Testing the Donation Logic -Testing ensures that the donation logic works as expected, verifying the equal distribution of funds among administrators. - -```rust -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{coins, from_binary}; - - #[test] - fn proper_donation_distribution() { - let mut deps = mock_dependencies(&coins(100, "token")); - - let instantiate_msg = InstantiateMsg { - admins: vec!["admin1".to_string(), "admin2".to_string()], - donation_denom: "token".to_string(), - }; - - let info = mock_info("creator", &coins(100, "token")); - instantiate(deps.as_mut(), mock_env(), info, instantiate_msg).unwrap(); - - let donate_info = mock_info("donor", &coins(50, "token")); - let donate_msg = ExecuteMsg::Donate {}; - let _ = execute(deps.as_mut(), mock_env(), donate_info, donate_msg).unwrap(); - - // Assertions to verify the correct distribution of donations - } -} -``` -## Conclusion - -This practical example illustrates the process of managing funds within a CosmWasm 2.0 smart contract, from defining messages and managing state to executing financial transactions and testing. By leveraging the framework's powerful features, developers can implement sophisticated financial mechanisms, such as the donation system described, enhancing the functionality and interactivity of their smart contracts in the Cosmos ecosystem. \ No newline at end of file diff --git a/src/part1/section-3-advanced/1.8.contract-state.md b/src/part1/section-3-advanced/1.8.contract-state.md deleted file mode 100644 index f1131a8..0000000 --- a/src/part1/section-3-advanced/1.8.contract-state.md +++ /dev/null @@ -1,338 +0,0 @@ -# Managing Contract State in CosmWasm 2.0 - -In this chapter, we delve into the concept of state in CosmWasm smart contracts, enabling dynamic and interactive contract behavior. Initially, the state will be initialized upon contract instantiation, containing a list of admins with exclusive execution privileges. - -## Updating Dependencies -First, ensure your Cargo.toml reflects the latest dependencies, crucial for state management in CosmWasm 2.0: - -```rust -[package] -name = "contract" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -cosmwasm-std = "2.0.0" # Updated for CosmWasm 2.0 -serde = { version = "1.0", default-features = false, features = ["derive"] } -cw-storage-plus = "0.14.0" # Updated version for enhanced state management - -[dev-dependencies] -cw-multi-test = "0.14.0" # Ensure compatibility with CosmWasm 2.0 - -``` - -## State Definition - -Create src/state.rs to define the contract's state structure: - -```rust -use cosmwasm_std::Addr; -use cw_storage_plus::Item; - -// Admins storage, utilizing the Item type for simple key-value storage -pub const ADMINS: Item> = Item::new("admins"); - -// Declare the state module in src/lib.rs: - -mod state; -``` -The `ADMINS` constant, an `Item>`, doesn't store the state itself but acts as an accessor to the blockchain's state managed through the ``deps` argument in entry points. - -## Instantiation Message Update - -Revise `src/msg.rs` to include an `InstantiateMsg` that lists admins: -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub struct InstantiateMsg { - pub admins: Vec, -} -``` -## State Initialization - -Update the instantiation logic in src/contract.rs to initialize the state with the provided admins list: -```rust -use crate::state::ADMINS; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let admins: StdResult> = msg - .admins - .into_iter() - .map(|addr| deps.api.addr_validate(&addr)) - .collect(); - ADMINS.save(deps.storage, &admins?)?; - - Ok(Response::new()) -} -``` -Ensure the entry point in src/lib.rs is updated accordingly -```rust -use msg::InstantiateMsg; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - contract::instantiate(deps, env, info, msg) -} -``` -## Testing State Initialization -Implement a test in `src/contract.rs` to verify state initialization affects the contract as expected. Include a query to list all admins: - -```rust -// Add a variant for the query message and a response message -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub struct AdminsListResp { - pub admins: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub enum QueryMsg { - Greet {}, - AdminsList {}, -} - -// Implement the query in the contract -mod query { - pub fn admins_list(deps: Deps) -> StdResult { - let admins = ADMINS.load(deps.storage)?; - Ok(AdminsListResp { admins }) - } -} -``` - -The new thing we have here is the ADMINS constant of type Item>. You could ask an excellent question here - how is the state constant? How do I modify it if it is a constant value? - -The answer is tricky - this constant is not keeping the state itself. The state is stored in the blockchain and is accessed via the deps argument passed to entry points. The storage-plus constants are just accessor utilities helping us access this state in a structured way. - -In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with metainformation pointing to the contract which owns them (so no other contract can alter them in any way), but even after removing the prefixes, the single contract state is a smaller key-value pair. - -storage-plus handles more complex state structures by additionally prefixing items keys intelligently. For now, we just used the simplest storage entity - an Item<_>, which holds a single optional value of a given type - Vec in this case. And what would be a key to this item in the storage? It doesn't matter to us - it would be figured out to be unique, based on a unique string passed to the new function. - -Before we would go into initializing our state, we need some better instantiate message. Go to src/msg.rs and create one: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub struct InstantiateMsg { - pub admins: Vec, -} - -// Now go forward to instantiate the entry point in src/contract.rs, and initialize our state to whatever we got in the instantiation message: - - -use crate::state::ADMINS; -// --snip-- -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - let admins: StdResult> = msg - .admins - .into_iter() - .map(|addr| deps.api.addr_validate(&addr)) - .collect(); - ADMINS.save(deps.storage, &admins?)?; - - Ok(Response::new()) -} -``` -We also need to update the message type on entry point in src/lib.rs: - -```rust -use msg::InstantiateMsg; -// --snip-- -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - contract::instantiate(deps, env, info, msg) -} -``` -Voila, that's all that is needed to update the state! - -First, we need to transform the vector of strings into the vector of addresses to be stored. We cannot take addresses as a message argument because not every string is a valid address. It might be a bit confusing when we were working on tests. Any string could be used in the place of address. Let me explain. - -Every string can be technically considered an address. However, not every string is an actual existing blockchain address. When we keep anything of type Addr in the contract, we assume it is a proper address in the blockchain. That is why the addr_validate function exits - to check this precondition. - -Having data to store, we use the save function to write it into the contract state. Note that the first argument of save is &mut Storage, which is actual blockchain storage. As emphasized, the Item object stores nothing and is just an accessor. It determines how to store the data in the storage given to it. The second argument is the serializable data to be stored. - -It is a good time to check if the regression we have passes - try running our tests: - - -> cargo test - -... - -running 1 test -test contract::tests::greet_query ... FAILED - -failures: - ----- contract::tests::greet_query stdout ---- -thread 'contract::tests::greet_query' panicked at 'called `Result::unwrap()` on an `Err` value: error executing WasmMsg: -sender: owner -Instantiate { admin: None, code_id: 1, msg: Binary(7b7d), funds: [], label: "Contract" } - -Caused by: - Error parsing into type contract::msg::InstantiateMsg: missing field `admins`', src/contract.rs:80:14 -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - - -failures: - contract::tests::greet_query - -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - -error: test failed, to rerun pass '--lib' -Damn, we broke something! But be calm. Let's start with carefully reading an error message: - -Error parsing into type contract::msg::InstantiateMsg: missing field admins', src/contract.rs:80:14 - -The problem is that in the test, we send an empty instantiation message in our test, but right now, our endpoint expects to have an admin field. Multi-test framework tests contract from the entry point to results, so sending messages using MT functions first serializes them. Then the contract deserializes them on the entry. But now it tries to deserialize the empty JSON to some non-empty message! We can quickly fix it by updating the test: - -```rust - #[test] - fn greet_query() { - let mut app = App::default(); - - let code = ContractWrapper::new(execute, instantiate, query); - let code_id = app.store_code(Box::new(code)); - - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("owner"), - &InstantiateMsg { admins: vec![] }, - &[], - "Contract", - None, - ) - .unwrap(); - - let resp: GreetResp = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::Greet {}) - .unwrap(); - - assert_eq!( - resp, - GreetResp { - message: "Hello World".to_owned() - } - ); - } -``` - -## Testing state -When the state is initialized, we want a way to test it. We want to provide a query to check if the instantiation affects the state. Just create a simple one listing all admins. Start with adding a variant for query message and a corresponding response message in src/msg.rs. We'll call the variant AdminsList, the response AdminsListResp, and have it return a vector of cosmwasm_std::Addr: - -```rust -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub struct AdminsListResp { - pub admins: Vec, -} - -[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -pub enum QueryMsg { - Greet {}, - AdminsList {}, -} -And implement it in src/contract.rs: - - -use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; -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)?), - } -} - -mod query { - pub fn admins_list(deps: Deps) -> StdResult { - let admins = ADMINS.load(deps.storage)?; - let resp = AdminsListResp { admins }; - Ok(resp) - } -} -``` - -Now when we have the tools to test the instantiation, let's write a test case: - -```rust -use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg}; -#[cfg(test)] -mod tests { - #[test] - fn instantiation() { - let mut app = App::default(); - - let code = ContractWrapper::new(execute, instantiate, query); - let code_id = app.store_code(Box::new(code)); - - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("owner"), - &InstantiateMsg { admins: vec![] }, - &[], - "Contract", - None, - ) - .unwrap(); - - let resp: AdminsListResp = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::AdminsList {}) - .unwrap(); - - assert_eq!(resp, AdminsListResp { admins: vec![] }); - - let addr = app - .instantiate_contract( - code_id, - Addr::unchecked("owner"), - &InstantiateMsg { - admins: vec!["admin1".to_owned(), "admin2".to_owned()], - }, - &[], - "Contract 2", - None, - ) - .unwrap(); - - let resp: AdminsListResp = app - .wrap() - .query_wasm_smart(addr, &QueryMsg::AdminsList {}) - .unwrap(); - - assert_eq!( - resp, - AdminsListResp { - admins: vec![Addr::unchecked("admin1"), Addr::unchecked("admin2")], - } - ); - } -} -``` -The test is simple - instantiate the contract twice with different initial admins, and ensure the query result is proper each time. This is often the way we test our contract - we execute bunch o messages on the contract, and then we query it for some data, verifying if query responses are like expected. - -We are doing a pretty good job developing our contract. Now it is time to use the state and allow for some executions. \ No newline at end of file diff --git a/src/part1/section-3-advanced/1.9.execution-messages.md b/src/part1/section-3-advanced/1.9.execution-messages.md deleted file mode 100644 index 5652eeb..0000000 --- a/src/part1/section-3-advanced/1.9.execution-messages.md +++ /dev/null @@ -1,15 +0,0 @@ -# Execution Messages in CosmWasm 2.0 - -## Introduction to Execution Messages - -Execution messages in CosmWasm smart contracts serve as the core mechanism for performing state-altering actions. These messages enable a broad spectrum of functionalities, ranging from simple administrative adjustments to the implementation of complex business logic within the contract. - -## Setting the Stage -Execution messages play a pivotal role in smart contract administration, facilitating essential governance actions such as: - -- **Adding members** to the administrator list. -- **Allowing administrators** to leave or remove themselves from the list. -This section explores how to manage contract administrators effectively through execution messages. - -## Defining Execution Messages -Execution messages are defined within the ExecuteMsg enum, providing a structured approach to specifying administrative actions: \ No newline at end of file diff --git a/src/part1/section-4-best-practices/1.12good-practices.md b/src/part1/section-4-best-practices/1.12good-practices.md deleted file mode 100644 index 57e4536..0000000 --- a/src/part1/section-4-best-practices/1.12good-practices.md +++ /dev/null @@ -1,97 +0,0 @@ -# Good Practices in CosmWasm 2.0 - -With the foundation set, it's time to refine our approach with best practices that align with CosmWasm 2.0 developments. These practices ensure our contracts are not only efficient and secure but also maintainable and easy to interact with. - -## JSON Renaming for Compatibility -CosmWasm embraces Rust's camelCase convention for naming. However, the JSON world predominantly uses snake_case. To bridge this stylistic gap, Serde attributes offer a seamless way to ensure our JSON keys match the expected case without manual string manipulation. -```rust -use cosmwasm_std::Addr; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub struct InstantiateMsg { - pub admins: Vec, - pub donation_denom: String, -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - AddMembers { admins: Vec }, - Leave {}, - Donate {}, -} -// Additional structs and enums follow the same pattern... -``` -This minor annotation ensures our contract's external API adheres to the JSON naming conventions, enhancing interoperability and ease of use. - -## Leveraging JSON Schema for API Documentation -Defining the shape of JSON messages via a schema is a robust practice to document and validate the contract's API. Writing schemas by hand is cumbersome, but automation comes to the rescue with the cosmwasm-schema crate. This tool generates schemas directly from our Rust code, ensuring accuracy and saving time. - -To integrate this functionality, we adjust Cargo.toml to include necessary dependencies and make a slight modification to our message structs: -```rust -# In Cargo.toml -[dependencies] -schemars = "0.8.1" -cosmwasm-schema = "1.1.4" -``` -Then, we annotate our message structs with JsonSchema to auto-generate the schema documentation: - -```rust -use schemars::JsonSchema; - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct InstantiateMsg { - // Fields... -} -// Repeat for other message structs... -``` -Simplifying Boilerplate with `cw_serde` -The `cosmwasm-schema` crate's `cw_serde` macro further simplifies our code by wrapping common derive macros, reducing boilerplate: - -```rust -use cosmwasm_schema::cw_serde; - -#[cw_serde] -pub struct InstantiateMsg { - // Fields... -} -// Repeat for other message structs... -``` -## QueryResponses for Enhanced Clarity -For query messages, correlating variants with their responses is streamlined using the QueryResponses trait, which mandates specifying return types: -```rust -use cosmwasm_schema::{cw_serde, QueryResponses}; - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(GreetResp)] - Greet {}, - // Other variants... -} -``` -This explicit mapping enhances clarity and type safety, ensuring that the contract's query API is well-defined and predictable. - -## Optimizing Contract Code for Library Use - -When our contract is intended for use as a dependency, we must avoid duplicate Wasm entry points. This is achieved by conditionally compiling entry points only when the contract is not used as a library: - -```rust -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - // Parameters... -) -> StdResult { - // Implementation... -} -``` -By adding a `library` feature in `Cargo.toml`, we can toggle this behavior based on the contract's use case, ensuring compatibility and preventing compilation issues: -```rust -[features] -library = [] -``` -## Conclusion - -Adopting these good practices not only aligns our CosmWasm contracts with the latest standards but also ensures they are developer-friendly, maintainable, and seamlessly integrate with the broader ecosystem. By leveraging Serde for JSON compatibility, automating schema generation, reducing boilerplate, and optimizing for library usage, we set a solid foundation for building robust and interoperable smart contracts in CosmWasm 2.0. \ No newline at end of file diff --git a/src/part1/section-4-best-practices/1.13floating-point-types.md b/src/part1/section-4-best-practices/1.13floating-point-types.md deleted file mode 100644 index 5f61ec3..0000000 --- a/src/part1/section-4-best-practices/1.13floating-point-types.md +++ /dev/null @@ -1,29 +0,0 @@ -# Floating Point Types and Their Limitations in CosmWasm -As you venture into the realm of smart contract development with CosmWasm, it's crucial to understand certain limitations, particularly regarding floating-point numbers. The use of floating-point types is a significant consideration that developers must navigate carefully. - -## The Fundamental Restriction -In the context of CosmWasm smart contracts, floating-point numbers (e.g., `f32`, `f64`) are fundamentally prohibited. This restriction is by design, as the CosmWasm virtual machine explicitly omits the implementation of floating-point WebAssembly instructions, including basic operations like `F32Load`. The rationale behind this decision is rooted in the need for deterministic and safe operations in the blockchain environment. - -Attempting to deploy a contract containing floating-point operations to a blockchain will result in an error during the upload process. This error indicates the presence of unsupported floating-point operations within the contract code. To assist developers in ensuring contract compliance, the `cosmwasm-check` utility can be used to validate contracts against these constraints. - -## Navigating the Floating-Point Constraint -Given this limitation, developers are encouraged to employ alternative strategies for numerical operations within their contracts. Specifically, CosmWasm provides `Decimal` and `Decimal256` types to facilitate decimal or fixed-point arithmetic, offering a robust solution for handling numerical calculations without relying on floating-point types. - -## Watch Out for External Crates -A less obvious implication of the floating-point restriction is the need for vigilance when incorporating external crates into your contract. Certain operations or types within popular crates might inadvertently introduce floating-point operations into your contract code. For example, serde's deserialization of `usize` (or `isize`) employs floating-point operations, making these types unsuitable for contracts requiring serialization or deserialization. - -Additionally, the deserialization of untagged enums using `serde` can also lead to issues. To circumvent this, developers can leverage the `serde-cw-value` crate, a fork of serde-value specifically modified for CosmWasm to avoid generating floating-point instructions. This crate provides a pathway for custom deserialization processes that maintain compliance with CosmWasm's limitations. - -## Practical Advice for Developers - -- **Utilize `Decimal` and `Decimal256`:** For numerical calculations, prefer CosmWasm's built-in types that are designed to work within the platform's constraints. -- **Audit External Dependencies:** Carefully review any external crates for potential floating-point operations, especially those involved in serialization and deserialization. -- **Employ `serde-cw-value` for Custom Deserialization:** When dealing with complex data structures that require deserialization, `serde-cw-value` offers a compliant solution. - -## Conclusion -Understanding and adapting to the limitations regarding floating-point types in CosmWasm is essential for developing secure, deterministic, and blockchain-compatible smart contracts. By leveraging the provided alternatives and being mindful of the implications of external dependencies, developers can navigate these constraints effectively, ensuring their contracts are robust and compliant with the CosmWasm platform. - - - - - diff --git a/src/part2/deep-dive-cosmos.md b/src/part2/deep-dive-cosmos.md deleted file mode 100644 index bdd3f52..0000000 --- a/src/part2/deep-dive-cosmos.md +++ /dev/null @@ -1,39 +0,0 @@ -# Part II: Deep Dive into CosmWasm 2.0 - -## 4. Advanced Features in 2.0 - -### Exploring the New Horizon - -CosmWasm 2.0 introduces a range of advanced features and improvements, marking a significant evolution from its previous version. In this section, we delve into these enhancements to understand how they contribute to a more robust and efficient smart contract environment. - -### Feature Breakdown and Enhancements - -### Performance Improvements - -CosmWasm 2.0 brings several optimizations to the execution environment: - -- **Enhanced Execution Speed**: Detailing how the new version achieves faster contract execution times. -- **Resource Management**: Explaining improvements in how CosmWasm handles computational resources, leading to more efficient contract performance. - -#### Security Upgrades - -Version 2.0 introduces crucial security features: - -- **New Security Protocols**: Overview of the implemented security measures that address vulnerabilities found in previous versions. -- **Contract Integrity**: Discussion on how these security enhancements contribute to the overall strength and trustworthiness of contracts. - -#### Developer Tooling Enhancements - -The toolset in CosmWasm 2.0 has been significantly upgraded: - -- **Debugging and Testing**: Exploration of the new debugging and testing capabilities, and how they streamline the development process. -- **Developer Experience**: Insights into how these tools improve the overall experience for developers working with CosmWasm. - -### Version Comparison: 1.5 vs. 2.0 - -A detailed comparison of the differences between versions 1.5 and 2.0: - -- **Key Feature Changes**: Discussing the most notable updates and their implications. -- **Impact on Contracts and Development**: Analyzing how these changes affect existing contracts and influence development practices moving forward. - -Through this exploration of CosmWasm 2.0's advanced features, developers and users alike can gain a comprehensive understanding of the framework's capabilities, setting a foundation for effective and innovative contract development. diff --git a/src/part2/interacting-with-contracts.md b/src/part2/interacting-with-contracts.md deleted file mode 100644 index 2d66e2e..0000000 --- a/src/part2/interacting-with-contracts.md +++ /dev/null @@ -1,92 +0,0 @@ -# 6. Interacting with Contracts - -## Effective Contract Interaction - -This section is dedicated to understanding how to interact with smart contracts in CosmWasm effectively. We'll cover managing contract state, executing operations, and the nuances of data interaction. - -## Managing Contract State - -Efficient state management is crucial for the smooth operation of smart contracts. This part discusses state management in CosmWasm contracts. - -### State Management Techniques - -- **State Storage Strategies**: Best practices for storing and retrieving state within your contracts. -- **State Migration**: How to handle state migration during contract upgrades. - -```rust -#[cw_serde] -struct Config { - owner: Addr, - amount: Uint128, - // using a struct for more complex state allows you to add more fields later on without breaking the contract -} - -const CONFIG: Item = Item::new("config"); - -/// Maps an address to a balance -/// You can use this to store a balance per address and load it later -const BALANCE: Map = Map::new("balance"); -``` - -### Sending and Querying Data - -Interacting with contracts involves sending transactions and querying data. This section provides detailed guidance on these operations. - -### Sending Transactions - -- Executing Contract Functions: How to send transactions to execute contract functions. -- Handling Responses: Understanding the responses from contract execution. - -```rust -// Example Rust code for sending a transaction -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // To send sub-messages to other contracts or the chain, you add them to the Response that you return - Ok(Response::new().add_message(WasmMsg::Execute { - contract_addr: msg.contract_addr, - // OtherContractMsg is the message type that the other contract will understand. - // Many open source contracts will provide their types as a crate you can use. - msg: to_json_binary(&OtherContractMsg::Send { - amount: Uint128::new(100), - })?, - funds: vec![], - })) -} -``` - -### Querying Contract Data - -- Query Mechanics: How to query data from a contract. -- Response Formats: Understanding the different formats of query responses. -- Off-Chain Interactions: Handling interactions with off-chain data sources. - -```rust -// Example Rust code for querying contract data -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - // OtherContractQuery and OtherContractExchangeRateResp are the types the other contract expects / returns - // Many open source contracts will provide their types as a crate you can use. - let response: OtherContractExchangeRateResp = deps - .querier - .query_wasm_smart(msg.contract_addr, &OtherContractQuery::ExchangeRate {})?; - - // now you can use the response data for your contract logic - let amount = response.rate * Uint128::new(100); - // ... - - Ok(Response::default()) -} -``` - -You can also query data from the blockchain using the `deps.querier` object: - -```rust -// Example Rust code for querying chain data -#[entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { - // Query the balance of the sender for the given denomination - let coin = deps.querier.query_balance(info.sender, msg.denom)?; - // now you can use the response data for your contract logic - to_json_binary(&coin.amount) -} -``` diff --git a/src/part2/smart-contract-development.md b/src/part2/smart-contract-development.md deleted file mode 100644 index 3987d4d..0000000 --- a/src/part2/smart-contract-development.md +++ /dev/null @@ -1,99 +0,0 @@ -# 5. Smart Contract Development - -## Writing Contracts Like a Pro - -As we move beyond the basics, this section is dedicated to best practices in smart contract development within the CosmWasm ecosystem. The goal is to ensure that contracts are not only functional but also efficient, secure, and easy to maintain. - -## Best Practices in Contract Development - -### Code Organization - -Good code organization is key to maintaining and scaling smart contracts. Here are some tips: - -- **Modular Design**: Break your contract into smaller, reusable modules. -- **Clear Documentation**: Comment your code and maintain updated documentation. -- **Version Control**: Use version control systems like Git to manage changes and collaborate. - -### Security Practices - -Security is paramount in smart contract development. Pay attention to: - -- **Input Validation**: Always validate inputs to prevent injections and other attacks. -- **Error Handling**: Implement comprehensive error handling to avoid unexpected failures. -- **Audits and Reviews**: Regularly audit your code and participate in peer reviews. - -## Advanced Contract Functionalities - -In CosmWasm, smart contracts can be equipped with advanced functionalities: - -- **Complex State Management**: Strategies for handling sophisticated state mechanisms within contracts. -- **External Data Integration**: Techniques for integrating external data sources safely and efficiently. -- **Optimizing Contract Logic**: Best practices for optimizing contract execution and resource usage. - -### Example: Advanced State Management - -```rust -// Example Rust code demonstrating advanced state management in CosmWasm - -#[cw_serde] -struct Person { - pub name: String, - pub last_name: String, - pub age: u32, -} - -#[index_list(Person)] -struct PersonIndexes<'a> { - pub name_lastname: UniqueIndex<'a, (Vec, Vec), Person, String>, -} - -/// This stores our person data. We want to be able to access it by the key we save it with, -/// but also by the combination of name and last_name, so we use an IndexedMap -const PERSON: IndexedMap<&str, Person, PersonIndexes> = IndexedMap::new( - "data", - PersonIndexes { - name_lastname: UniqueIndex::new( - |d| (d.name.as_bytes().to_vec(), d.last_name.as_bytes().to_vec()), - "data__name_lastname", - ), - }, -); - -// save some people -let maria_williams = Person { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 32, -}; -PERSON.save(&mut store, "1", &maria_williams).unwrap(); -let maria_park = Person { - name: "Maria".to_string(), - last_name: "Park".to_string(), - age: 28, -}; -PERSON.save(&mut store, "2", &maria_park).unwrap(); - -// we can load from the key we used to save -let person = PERSON.load(&store, "1").unwrap(); -assert_eq!(person, maria_williams); - -// but also from the index using the full name -let (key, d) = PERSON - .idx - .name_lastname - .item( - &store, - ("Maria".as_bytes().to_vec(), "Williams".as_bytes().to_vec()), - ) - .unwrap() - .unwrap(); -assert_eq!(key, b"1"); -assert_eq!(d, maria_williams); -``` - -### Example Integrating External Data - -``` -// Example Rust code for integrating external data sources -// Placeholder for external data integration example -``` diff --git a/src/part3/ibc-intergration-and-crosschain.md b/src/part3/ibc-intergration-and-crosschain.md deleted file mode 100644 index 25ae8c5..0000000 --- a/src/part3/ibc-intergration-and-crosschain.md +++ /dev/null @@ -1,107 +0,0 @@ -# Chapter 7: IBC Integration and Cross-Chain Communication in CosmWasm - -## Embracing the Interconnected Future - -The dawn of blockchain interoperability heralds a transformative era for decentralized applications (dApps). In this chapter, we delve into the integration of the Inter-Blockchain Communication (IBC) protocol with CosmWasm, a pivotal advancement that bridges isolated blockchain ecosystems. This synergy not only amplifies the potential of dApps but also lays the foundation for a seamlessly interconnected blockchain universe. - -## **Why** Integrate IBC with CosmWasm? -Imagine a world where assets flow freely across blockchains, where a dApp on one chain can leverage capabilities from another without friction. This vision drives the integration of IBC with CosmWasm, offering: - -- **Interoperability:** Breaking down the barriers between chains to create a unified blockchain ecosystem. -- **Enhanced dApp Functionality:** Enabling complex dApps that draw on the strengths of multiple blockchains, such as cross-chain DeFi platforms and expansive NFT marketplaces. -- **Accessibility and Liquidity:** Facilitating asset transfers across chains, broadening access and enhancing liquidity. - -## **The How:** Implementing IBC in CosmWasm -IBC integration in CosmWasm is a journey through setting up IBC clients, establishing connections and channels, and mastering the art of sending and receiving packets. Let's embark on this journey, equipped with code snippets and insights to illuminate the path. - -## **Step 1:** Setting Up IBC Clients -IBC clients act as the gatekeepers, managing the state of blockchains involved in communication. Establishing an IBC client involves specifying the client type and providing blockchain consensus state information. This setup ensures that each chain can trust the state information it receives from its counterpart. -```rust -// Function to initialize an IBC client, specifying its type and consensus state. -// This is a foundational step in enabling secure cross-chain communication. -fn init_ibc_client(client_type: String, consensus_state: ConsensusState) { - // Example pseudo-code to initialize a client. In practice, you would - // interact with the IBC modules provided by the CosmWasm framework. - let client_id = ibc::client::create_client(client_type, consensus_state); - // Store the `client_id` for future reference, such as when establishing connections. -} -``` -//Diagram placement - -## **Step 2:** Establishing Connections and Channels -With IBC clients in place, the next phase involves crafting the conduits for communication — connections and channels. Connections ensure the secure exchange between chains, while channels define the nature of the data or assets being transferred. - -**Creating a Connection:** - -```rust -// Establishes an IBC connection using the client IDs of both participating blockchains. -// This involves a multi-step handshake that confirms mutual agreement on the connection's parameters. -fn create_connection(local_client_id: String, remote_client_id: String) { - // Start the handshake process. This would involve calling IBC-related functions - // to initiate the connection and agreeing on the parameters. - let connection_id = ibc::connection::open_init(local_client_id, remote_client_id); - // Finalize the connection after the handshake is complete. - // The `connection_id` is saved for future channel creation and packet transfers. -} - -``` -**Opening a Channel:** - -```rust -// Opens an IBC channel on an established connection, defining the properties of the data or assets to be transferred. -fn open_channel(connection_id: String, port_id: String, channel_order: IbcOrder, data_type: String) { - // Configure and open a channel on the specified connection. - // The `data_type` parameter could specify the nature of the data being transferred, such as tokens or messages. - let channel_id = ibc::channel::open_init(connection_id, port_id, channel_order, data_type); - // The `channel_id` is crucial for sending and receiving packets over this channel. -} -``` -// Diagram: Architecture of connections and channels - -**Step 3:** Sending and Receiving Packets -The essence of IBC lies in the packets that traverse these channels. These packets can encapsulate a myriad of data types, facilitating a spectrum of cross-chain interactions. - -**Sending a Packet:** -```rust -// Sends a packet containing data or assets to a recipient on another blockchain. -// This demonstrates how to construct and dispatch a packet over an established IBC channel. -fn send_packet(channel_id: String, data: Binary, timeout: IbcTimeout) { - // Construct the packet with the provided data and timeout. - // The `Binary` type encapsulates the data being sent, which could be encoded information or a token transfer command. - let packet = IbcPacket::new(channel_id, data, timeout); - // Send the packet over the specified channel. - ibc::packet::send_packet(packet); -} -``` -**Receiving a Packet:** -```rust -// Receives an IBC packet and processes its contents according to the contract's logic. -// This function is called automatically by the IBC module when a packet is received. -fn receive_packet(packet: IbcPacket) -> StdResult { - // Parse and validate the incoming packet's data. - let data: MyPacketData = from_binary(&packet.data)?; - // Process the packet data. This could involve state updates, token transfers, or other operations. - process_packet_data(data)?; - // Construct and return an acknowledgment response. - Ok(IbcReceiveResponse::new().set_ack(ack_success())) -} - -// Note: These examples use pseudo-functions like `ibc::client::create_client` and `process_packet_data`, -// which are not part of the actual CosmWasm framework. They are intended to illustrate the kind of operations -// you might perform in a real-world IBC integration within a CosmWasm contract. - -//diagram -``` -## IBC in Action: Real-World Applications -To crystallize our understanding, let's explore practical applications of IBC integration. These case studies exemplify how cross-chain dApps can revolutionize sectors like DeFi and NFTs. - -### Case Study 1: Cross-Chain DeFi Platforms - -Imagine a DeFi platform that seamlessly aggregates liquidity across Ethereum, Binance Smart Chain, and Terra. Through IBC, this platform can offer users unparalleled access to diverse assets and liquidity pools, optimizing yields and reducing slippage. - -### Case Study 2: Expansive NFT Marketplaces - -Consider an NFT marketplace that spans multiple blockchains, allowing creators to mint and sell NFTs on their chain of choice while reaching collectors across the ecosystem. IBC facilitates this expansive marketplace, driving innovation and inclusivity in the digital art world. - -## Conclusion: The IBC-CosmWasm Nexus -The integration of IBC with CosmWasm is not just a technical milestone; it's a paradigm shift towards an interconnected blockchain landscape. By mastering IBC, developers can unlock new horizons for dApps, fostering a future where blockchains operate not as isolated islands but as a cohesive, interconnected ecosystem. As we forge ahead, the role of IBC in shaping the blockchain universe will undoubtedly grow, marking a new chapter in the evolution of decentralized technologies. \ No newline at end of file diff --git a/src/part3/optimizing-contract-performance.md b/src/part3/optimizing-contract-performance.md deleted file mode 100644 index 3fd3f27..0000000 --- a/src/part3/optimizing-contract-performance.md +++ /dev/null @@ -1,69 +0,0 @@ -# Part III: Specialized Topics - -## 8. Optimizing Contract Performance - -### Enhancing Contract Efficiency - -In the blockchain environment, optimizing the performance of smart contracts is crucial. This section focuses on techniques and best practices to enhance the efficiency and speed of CosmWasm contracts. - -## Performance Considerations and Tips - -Performance in smart contracts can be influenced by various factors. Understanding and optimizing these factors is key to developing efficient contracts. - -### Factors Impacting Performance - -- **Contract Design**: How the overall structure and logic of the contract can affect its performance. -- **Data Storage and Retrieval**: Efficient ways to store and access data in the contract. -- **Transaction Optimization**: Minimizing the computational cost of transactions. - -### Actionable Optimization Strategies - -- **Resource Management**: Effective techniques for managing computational and storage resources. -- **Caching Mechanisms**: Implementing caching to reduce redundant computations. -- **Asynchronous Operations**: Utilizing asynchronous patterns where applicable to enhance performance. - -## Leveraging Rust Optimizations - -Rust offers several features and optimizations that are beneficial for smart contract development in CosmWasm. - -### Rust Language Features - -- **Memory Management**: Utilize Rust’s ownership and borrowing model for efficient memory usage. -- **Type Safety and Concurrency**: Leverage Rust's strong type system and concurrency capabilities for robust and efficient contract code. - -### Writing Efficient Rust Code for Smart Contracts - -- **Optimal Data Structures**: Choosing the right data structures for various scenarios in smart contract development. -- **Algorithmic Efficiency**: Writing algorithms that minimize computational complexity. - -Most optimizations are very specific to the contract's use case and specific requirements, -but choosing the right data structure is the most important factor. - -For example, if you have some configuration you need almost everywhere in the contract, -it makes sense to put all of it into struct and store it as a single `Item` instead -of storing each field separately. This is the case because every load from storage incurs a gas -cost consisting of a flat and a variable part. For many small reads, the flat part starts to add up. -It is important to stress that this only makes sense if you need all or most of the fields at once anyways. - -```rust -// Example Rust code showcasing an optimization technique in CosmWasm -#[cw_serde] -struct Config { - owner: Addr, - receiver: Addr, - gift_amount: Coin, -} - -const CONFIG: Item = Item::new("config"); - -// then later in the contract: -let Config { - owner, - receiver, - gift_amount, -} = CONFIG.load(deps.storage)?; -// instead of: -// let owner = CONFIG.load(deps.storage)?.owner; -// let receiver = CONFIG.load(deps.storage)?.receiver; -// let gift_amount = CONFIG.load(deps.storage)?.gift_amount; -``` diff --git a/src/part3/security-and-best-practices.md b/src/part3/security-and-best-practices.md deleted file mode 100644 index 7442037..0000000 --- a/src/part3/security-and-best-practices.md +++ /dev/null @@ -1,69 +0,0 @@ -# Part III: Specialized Topics - -## 9. Security and Best Practices - -### Fortifying Smart Contracts - -Given the immutable and public nature of blockchain, security in smart contract development is of paramount importance. This section aims to cover comprehensive security considerations, best practices, and advanced mechanisms to build secure and reliable CosmWasm contracts. - -### Security Considerations in Development - -#### Critical Security Aspects - -- **Immutable Deployments**: The immutable nature of deployed contracts necessitates thorough testing and validation. -- **Public Accessibility**: The transparency of contracts on the blockchain requires robust coding practices to prevent exploitation. -- **Interactions with External Systems**: These can introduce vulnerabilities if not properly managed. - -#### Addressing Common Vulnerabilities - -- **Reentrancy Attacks**: CosmWasm's execution model mitigates these risks. -- **Integer Overflow and Underflow**: Rust’s type system and framework checks help prevent these issues. - -### Avoiding Common Pitfalls - -#### Recognizing and Mitigating Risks - -- **Poor State Management**: Crucial for contract reliability. -- **Insecure Randomness**: Demands secure methods for generating randomness. -- **Gas Limitations**: Excessive gas can lead to vulnerabilities. - -#### Testing and Auditing Best Practices - -- **Comprehensive Testing**: Essential at both unit and integration levels. -- **Third-party Audits**: Provide additional scrutiny. -- **Continuous Monitoring**: Necessary for identifying exploitation attempts. - -### Advanced Security Mechanisms - -Exploring sophisticated security techniques specific to blockchain and CosmWasm: - -- **Smart Contract Formal Verification**: Utilizing mathematical methods to verify the correctness of contracts. -- **Encryption and Privacy Techniques**: Implementing methods to enhance data privacy within contracts. - -### Case Studies of Security Breaches - -Analyzing past incidents to understand vulnerabilities and learn from them: - -- **Notable Breaches in Blockchain**: Examining specific cases and their outcomes. -- **Lessons Learned**: Drawing insights and best practices from these incidents. - -### Community and Ecosystem Support - -The role of the CosmWasm community in bolstering security: - -- **Collaborative Libraries and Tools**: Community-developed resources for enhanced security. -- **Discussion Forums and Support**: Platforms for developers to discuss and address security concerns. - -### Updates and Patch Management - -Handling updates in the immutable environment of blockchain: - -- **Best Practices for Contract Upgrades**: Strategies for updating and maintaining contracts post-deployment. -- **Versioning and Dependency Management**: Ensuring contract dependencies are secure and up-to-date. - -### Compliance and Legal Considerations - -Understanding the legal implications and compliance in smart contract security: - -- **Jurisdictional Variances**: How different regions' laws impact smart contract development. -- **Compliance Best Practices**: Ensuring contracts meet legal and regulatory standards. diff --git a/src/part4/case-studies.md b/src/part4/case-studies.md deleted file mode 100644 index e9396aa..0000000 --- a/src/part4/case-studies.md +++ /dev/null @@ -1,25 +0,0 @@ -# Part IV: Real-World Applications - -## 10. Case Studies - -### Bringing Theory to Life - -Understanding CosmWasm's impact and potential is best achieved through examining its real-world applications. This section delves into various projects that have leveraged CosmWasm, providing concrete examples of its practical usage in the blockchain space. - -### Project Analyses and Success Stories - -#### In-depth Case Studies - -We'll explore detailed case studies of notable projects built using CosmWasm: - -- **Project A**: An overview of the project’s objectives, the challenges it faced, and how CosmWasm facilitated its achievements. -- **Project B**: Analysis of another successful implementation, focusing on how CosmWasm's features were utilized to overcome specific hurdles and achieve the project's goals. - -```markdown -// Example case study structure -### Case Study: [Project Name] -- **Overview**: Brief introduction to the project. -- **Objectives**: What the project aimed to achieve. -- **Challenges**: Key challenges faced during development. -- **CosmWasm Implementation**: How CosmWasm was used in the project. -- **Outcomes**: Results and achievements of the project. diff --git a/src/part4/ecosystem-and-communication.md b/src/part4/ecosystem-and-communication.md deleted file mode 100644 index 6d50318..0000000 --- a/src/part4/ecosystem-and-communication.md +++ /dev/null @@ -1,44 +0,0 @@ -# Part IV: Real-World Applications - -## 11. Ecosystem and Community - -### A Thriving Network of Innovation - -The CosmWasm ecosystem extends beyond its technical framework, encompassing a vibrant community of developers, contributors, and enthusiasts. This section explores the various elements of the CosmWasm ecosystem, highlighting how it fits into the broader blockchain landscape and the ways one can actively participate in its growth. - -### Overview of the CosmWasm Ecosystem - -#### The CosmWasm Landscape - -- **Key Players and Projects**: Introducing the main contributors, developers, and projects that shape the CosmWasm ecosystem. -- **Integration in Blockchain Technology**: Discussing how CosmWasm interacts with and influences the wider blockchain and decentralized application space. - -#### The Role of CosmWasm - -- **Innovation and Development**: How CosmWasm fosters innovation in blockchain technology. -- **Community Contributions**: The impact of community-driven developments and initiatives on the ecosystem. - -### Engaging with the Community - -#### Getting Involved - -- **Participation Opportunities**: Various ways individuals and organizations can engage with the CosmWasm community, from coding and development to advocacy and support. -- **Community Resources**: Listing essential resources for further learning, such as official documentation, community forums, workshops, and webinars. - -#### Learning and Collaboration - -- **Educational Materials**: Guides, tutorials, and courses available for those looking to deepen their understanding of CosmWasm. -- **Networking and Events**: Information on community meetups, conferences, and online events to connect with fellow CosmWasm enthusiasts. - -### Fostering Growth and Collaboration - -#### The Importance of Community - -- **Collective Effort**: Highlighting the significance of community involvement in the ongoing development and success of CosmWasm. -- **Open Source Collaboration**: Encouraging contributions in various forms, including code contributions, documentation, bug reports, and feature suggestions. - -#### Call to Action - -- **Contributing to the Ecosystem**: Motivating readers to actively participate in the CosmWasm community, whether through technical contributions, sharing ideas, or engaging in discussions and forums. - -This section underscores the vital role of the community and ecosystem in the continual evolution and success of CosmWasm. It serves as an invitation for readers to become part of a thriving network, contributing to a future of innovative and collaborative blockchain development. diff --git a/src/setting-up-env.md b/src/setting-up-env.md new file mode 100644 index 0000000..a3d35b2 --- /dev/null +++ b/src/setting-up-env.md @@ -0,0 +1,86 @@ +# Setting up the environment + +To work with CosmWasm smart contract, you will need rust installed on your +machine. If you don't have one, you can find installation instructions on [the +Rust website](https://www.rust-lang.org/tools/install). + +I assume you are working with a stable Rust channel in this book. + +Additionally, you will need the Wasm rust compiler backend installed to build +Wasm binaries. To install it, run: + +``` +rustup target add wasm32-unknown-unknown +``` + +Optionally if you want to try out your contracts on a testnet, you will need a +[wasmd](https://github.com/CosmWasm/wasmd) binary. We would focus on testing +contracts with Rust unit testing utility throughout the book, so it is not +required to follow. However, seeing the product working in a real-world +environment may be nice. + +To install `wasmd`, first install the [golang](https://github.com/golang/go/wiki#working-with-go). Then +clone the `wasmd` and install it: + +``` +$ git clone git@github.com:CosmWasm/wasmd.git +$ cd ./wasmd +$ make install +``` + +Also, to be able to upload Rust Wasm Contracts into the blockchain, you will need +to install [docker](https://www.docker.com/). To minimize your contract sizes, +it will be required to run CosmWasm Rust Optimizer; without that, more complex +contracts might exceed a size limit. + +## cosmwasm-check utility + +An additional helpful tool for building smart contracts is the `cosmwasm-check`[utility](https://github.com/CosmWasm/cosmwasm/tree/main/packages/check). It allows you to check if the wasm binary is a proper smart contract ready to upload into the blockchain. You can install it using cargo: + +``` +$ cargo install cosmwasm-check +``` + +If the installation succeeds, you should be able to execute the utility from your command line. + +``` +$ cosmwasm-check --version +Contract checking 1.2.3 +``` + +## Verifying the installation + +To guarantee you are ready to build your smart contracts, you need to make sure you can build examples. +Checkout the [cw-plus](https://github.com/CosmWasm/cw-plus) repository and run the testing command in +its folder: + +``` +$ git clone git@github.com:CosmWasm/cw-plus.git +$ cd ./cw-plus +cw-plus $ cargo test +``` + +You should see that everything in the repository gets compiled, and all tests pass. + +`cw-plus` is a great place to find example contracts - look for them in `contracts` directory. The +repository is maintained by CosmWasm creators, so contracts in there should follow good practices. + +To verify the `cosmwasm-check` utility, first, you need to build a smart contract. Go to some contract directory, for example, `contracts/cw1-whitelist`, and call `cargo wasm`: + +``` +cw-plus $ cd contracts/cw1-whitelist +cw-plus/contracts/cw1-whitelist $ cargo wasm +``` + +You should be able to find your output binary in the `target/wasm32-unknown-unknown/release/` +of the root repo directory - not in the contract directory itself! Now you can check if contract +validation passes: + +``` +cw-plus/contracts/cw1-whitelist $ cosmwasm-check ../../target/wasm32-unknown-unknown/release/cw1_whitelist.wasm +Available capabilities: {"iterator", "cosmwasm_1_1", "cosmwasm_1_2", "stargate", "staking"} + +../../target/wasm32-unknown-unknown/release/cw1_whitelist.wasm: pass + +All contracts (1) passed checks! +``` diff --git a/src/wasmd-quick-start.md b/src/wasmd-quick-start.md new file mode 100644 index 0000000..a629cb3 --- /dev/null +++ b/src/wasmd-quick-start.md @@ -0,0 +1,8 @@ +# Quick start with `wasmd` + +In the past, we suggested playing with contracts on the `malaga` testnet using `wasmd`. +Now `malaga` is no longer operative, and the best way to test the contract in the +real environment is to use one of the big CosmWasm chains testnets - Osmosis, Juno, +Terra, or other ones. +[Here](https://docs.osmosis.zone/cosmwasm/testnet/cosmwasm-deployment/) is the +recommended introduction on how to start with the Osmosis testnet.