From 7ff5a96f99d9cbf344fa395969dc1e526be08fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Thu, 12 Dec 2024 18:19:13 +0100 Subject: [PATCH 1/4] Sylvia tutorial first message --- .../macros/generated-types/message-types.mdx | 2 +- src/pages/tutorial/sylvia-contract/_meta.json | 3 +- .../tutorial/sylvia-contract/entry-points.mdx | 80 +++++++++++++++++ .../sylvia-contract/first-messages.mdx | 88 +++++++++++++++++++ 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/pages/tutorial/sylvia-contract/entry-points.mdx create mode 100644 src/pages/tutorial/sylvia-contract/first-messages.mdx diff --git a/src/pages/sylvia/macros/generated-types/message-types.mdx b/src/pages/sylvia/macros/generated-types/message-types.mdx index 503b01cb..fbc1203f 100644 --- a/src/pages/sylvia/macros/generated-types/message-types.mdx +++ b/src/pages/sylvia/macros/generated-types/message-types.mdx @@ -21,7 +21,7 @@ be called depending on the enum variant. This is described in the example below The following code: -```rust, template="sylvia-empty" +```rust template="sylvia-empty" #[cw_serde] pub struct SomeResponse; diff --git a/src/pages/tutorial/sylvia-contract/_meta.json b/src/pages/tutorial/sylvia-contract/_meta.json index 8f9dc55c..8ce646e7 100644 --- a/src/pages/tutorial/sylvia-contract/_meta.json +++ b/src/pages/tutorial/sylvia-contract/_meta.json @@ -1,3 +1,4 @@ { - "contract-creation": "Contract creation" + "contract-creation": "Contract creation", + "first-messages": "First messages" } diff --git a/src/pages/tutorial/sylvia-contract/entry-points.mdx b/src/pages/tutorial/sylvia-contract/entry-points.mdx new file mode 100644 index 00000000..b1ed3ffb --- /dev/null +++ b/src/pages/tutorial/sylvia-contract/entry-points.mdx @@ -0,0 +1,80 @@ +# 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 executed. Unlike native applications, which have only a single `main` entry +point, smart contracts have a couple of them, each corresponding to different message type: +`instantiate`, `execute`, `query`, `sudo`, `migrate` and more. + +To start, we will go with three basic entry points: + +- **`instantiate`** 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 can 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 alter any contract state, and are used in a similar manner to + database queries. + +## Generate entry points + +^Sylvia provides an attribute macro named +[`entry_points`](https://docs.rs/sylvia/latest/sylvia/attr.entry_points.html). In most cases, your +entry point will just dispatch received messages to the handler, so it's not necessary to manually +create them, and we can rely on a macro to do that for us. + +Let's add the **`entry_points`** attribute macro to our contract: + +```rust,noplayground +use cosmwasm_std::{Response, StdResult}; +use sylvia::types::InstantiateCtx; +use sylvia::{contract, entry_points}; + +pub struct CounterContract; + +#[entry_points] +#[contract] +impl CounterContract { + pub const fn new() -> Self { + Self + } + + #[sv::msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult { + Ok(Response::default()) + } +} +``` + +Note that **`#[entry_points]`** is added above the **`#[contract]`**. It is because +**`#[contract]`** removes attributes like **`#[sv::msg(...)]`** on which both these macros rely. + +Always remember to place **`#[entry_points]`** first. + +^Sylvia generates entry points with +[`#[entry_point]`](https://docs.rs/cosmwasm-std/1.3.1/cosmwasm_std/attr.entry_point.html) attribute +macro. Its purpose is to wrap the whole entry point to the form the 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 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. + +Now, when our contract has a proper entry point, let's build it and check if it's correctly defined: + +```shell +contract $ cargo build --release --target wasm32-unknown-unknown --lib + Finished release [optimized] target(s) in 0.03s + +contract $ cosmwasm-check target/wasm32-unknown-unknown/release/contract.wasm +Available capabilities: {"stargate", "cosmwasm_1_3", "cosmwasm_1_1", "cosmwasm_1_2", "staking", "iterator"} + +target/wasm32-unknown-unknown/release/contract.wasm: pass + +All contracts (1) passed checks! +``` + +## Next step + +Well done! We have now a proper `CosmWasm` contract. Let's add some state to it, so it will actually +be able to do something. diff --git a/src/pages/tutorial/sylvia-contract/first-messages.mdx b/src/pages/tutorial/sylvia-contract/first-messages.mdx new file mode 100644 index 00000000..1573923f --- /dev/null +++ b/src/pages/tutorial/sylvia-contract/first-messages.mdx @@ -0,0 +1,88 @@ +# Generating first messages + +We have set up our dependencies. Now let's use them to create simple messages. + +## Creating an instantiation message + +For this step we will create a new file: + +- `src/contract.rs` - here, we will define our messages and behavior of the contract upon receiving + them + +Add this module to `src/lib.rs`. You want it to be public, as users might want to get access to +types stored inside your contract. + +```rust copy filename="src/lib.rs" +pub mod contract; +``` + +Now let's create an `instantiate` method for our contract. In `src/contract.rs` + +```rust copy filename="src/contract.rs" template="sylvia/empty" +use sylvia::cw_std::{Response, StdResult}; +use sylvia::contract; +use sylvia::ctx::InstantiateCtx; + +pub struct CounterContract; + +#[contract] +impl CounterContract { + pub const fn new() -> Self { + Self + } + + #[sv::msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult { + Ok(Response::default()) + } +} +``` + +So what is going on here? First, we define the `CounterContract` struct. It is empty right now but +later when we learn about states, we will use its fields to store them. We mark the `impl` block +with [`contract`](../../sylvia/macros/contract) attribute macro. It will parse every method inside +the `impl` block marked with the [`sv::msg(...)`](../../sylvia/macros/attributes/msg) attribute and +create proper messages and utilities like +[MultiTest helpers](../../sylvia/macros/generated-types/multitest) for them. More on them later. + +CosmWasm contract requires only the [instantiate](../../core/entrypoints/instantiate) entry point, +and it is mandatory to specify it for the [`contract`](../../sylvia/macros/contract) macro. We have +to provide it with the proper context type +[`InstantiateCtx`](https://docs.rs/sylvia/latest/sylvia/ctx/struct.InstantiateCtx.html). + +Context gives us access to the blockchain state, information about our contract, and the sender of +the message. We return the +[`StdResult`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/type.StdResult.html) which uses +standard CosmWasm error +[`StdError`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.StdError.html). It's generic over +[`Response`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Response.html). For now, we +will return the `default` value of it. + +I recommend expanding the macro now and seeing what Sylvia generates. It might be overwhelming, as +there will be a lot of things generated that seem not relevant to our code, so for the bare minimum +check the [`InstantiateMsg`](../../sylvia/macros/generated-types/message-types#contract-messages) +and its `impl` block. + +## Next step + +If we build our contract with command: + +```shell copy filename="TERMINAL" +cargo build --release --target wasm32-unknown-unknown --lib +``` + +and then run: + +```shell copy filename="TERMINAL" +cosmwasm-check target/wasm32-unknown-unknown/release/contract.wasm +``` + +The output should look like this: + +```shell filename="TERMINAL" +Available capabilities: {"stargate", "staking", "cosmwasm_1_3", "cosmwasm_2_0", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_4", "iterator"} + +target/wasm32-unknown-unknown/release/contract.wasm: pass + +All contracts (1) passed checks! +``` From 5d2c647e9a3161a4915cffd7dd8624fded923f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Thu, 12 Dec 2024 18:39:07 +0100 Subject: [PATCH 2/4] Sylvia tutorial entry points --- .../tutorial/sylvia-contract/entry-points.mdx | 74 ++++++++++--------- .../sylvia-contract/first-messages.mdx | 2 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/pages/tutorial/sylvia-contract/entry-points.mdx b/src/pages/tutorial/sylvia-contract/entry-points.mdx index b1ed3ffb..851f842c 100644 --- a/src/pages/tutorial/sylvia-contract/entry-points.mdx +++ b/src/pages/tutorial/sylvia-contract/entry-points.mdx @@ -1,33 +1,38 @@ +--- +tags: ["tutorial, sylvia"] +--- + +import { Callout } from "nextra/components"; + # 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 executed. Unlike native applications, which have only a single `main` entry -point, smart contracts have a couple of them, each corresponding to different message type: -`instantiate`, `execute`, `query`, `sudo`, `migrate` and more. +called [entry point](../../core/entrypoints) is executed. Unlike native applications, which have +only a single `main` entry point, smart contracts have a couple of them, each corresponding to +different message type. To start, we will go with three basic entry points: -- **`instantiate`** 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 can 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 alter any contract state, and are used in a similar manner to - database queries. +- [instantiate](../../core/entrypoints/instantiate) is called once per smart contract lifetime; you + can think about it as a constructor or initializer of a contract. +- [execute](../../core/entrypoints/execute) for handling messages which can modify contract state; + they are used to perform some actual actions. +- [query](../../core/entrypoints/query) for handling messages requesting some information from a + contract; unlike [execute](../../core/entrypoints/execute), they can never alter any contract + state, and are used in a similar manner to database queries. ## Generate entry points -^Sylvia provides an attribute macro named -[`entry_points`](https://docs.rs/sylvia/latest/sylvia/attr.entry_points.html). In most cases, your -entry point will just dispatch received messages to the handler, so it's not necessary to manually -create them, and we can rely on a macro to do that for us. +Sylvia provides an attribute macro named [`entry_points`](../../sylvia/macros/entry-points). In most +cases, your entry point will just dispatch received messages to the handler, so it's not necessary +to manually create them, and we can rely on a macro to do that for us. -Let's add the **`entry_points`** attribute macro to our contract: +Let's add the [`entry_points`](../../sylvia/macros/entry-points) attribute macro to our contract: -```rust,noplayground -use cosmwasm_std::{Response, StdResult}; -use sylvia::types::InstantiateCtx; +```rust {3, 7} copy filename="src/contract.rs" template="sylvia/empty" +use sylvia::ctx::InstantiateCtx; +use sylvia::cw_std::{Response, StdResult}; use sylvia::{contract, entry_points}; pub struct CounterContract; @@ -46,28 +51,31 @@ impl CounterContract { } ``` -Note that **`#[entry_points]`** is added above the **`#[contract]`**. It is because -**`#[contract]`** removes attributes like **`#[sv::msg(...)]`** on which both these macros rely. + + Note that [`entry_points`](../../sylvia/macros/entry-points) is added above the + [`contract`](../../sylvia/macros/contract). It is because + [`contract`](../../sylvia/macros/contract) removes attributes like + [`sv::msg(...)`](../../sylvia/macros/attributes/msg) on which both these macros rely. Always + remember to place [`entry_points`](../../sylvia/macros/entry-points) first. + -Always remember to place **`#[entry_points]`** first. - -^Sylvia generates entry points with -[`#[entry_point]`](https://docs.rs/cosmwasm-std/1.3.1/cosmwasm_std/attr.entry_point.html) attribute +Sylvia generates entry points with the [`entry_points`](../../sylvia/macros/entry-points) attribute macro. Its purpose is to wrap the whole entry point to the form the 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 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. +so CosmWasm creators delivered the +[`entry_point`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/attr.entry_point.html) 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. Now, when our contract has a proper entry point, let's build it and check if it's correctly defined: -```shell -contract $ cargo build --release --target wasm32-unknown-unknown --lib - Finished release [optimized] target(s) in 0.03s +```shell copy filename="TERMINAL" +cargo build --release --target wasm32-unknown-unknown --lib +``` -contract $ cosmwasm-check target/wasm32-unknown-unknown/release/contract.wasm -Available capabilities: {"stargate", "cosmwasm_1_3", "cosmwasm_1_1", "cosmwasm_1_2", "staking", "iterator"} +```shell filename="TERMINAL" +Available capabilities: {"cosmwasm_1_3", "cosmwasm_2_0", "cosmwasm_1_2", "stargate", "iterator", "cosmwasm_1_1", "cosmwasm_1_4", "staking", "cosmwasm_2_1"} target/wasm32-unknown-unknown/release/contract.wasm: pass @@ -76,5 +84,5 @@ All contracts (1) passed checks! ## Next step -Well done! We have now a proper `CosmWasm` contract. Let's add some state to it, so it will actually +Well done! We have now a proper CosmWasm contract. Let's add some state to it, so it will actually be able to do something. diff --git a/src/pages/tutorial/sylvia-contract/first-messages.mdx b/src/pages/tutorial/sylvia-contract/first-messages.mdx index 1573923f..d1338018 100644 --- a/src/pages/tutorial/sylvia-contract/first-messages.mdx +++ b/src/pages/tutorial/sylvia-contract/first-messages.mdx @@ -80,7 +80,7 @@ cosmwasm-check target/wasm32-unknown-unknown/release/contract.wasm The output should look like this: ```shell filename="TERMINAL" -Available capabilities: {"stargate", "staking", "cosmwasm_1_3", "cosmwasm_2_0", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_4", "iterator"} +Available capabilities: {"cosmwasm_1_3", "cosmwasm_2_0", "cosmwasm_1_2", "stargate", "iterator", "cosmwasm_1_1", "cosmwasm_1_4", "staking", "cosmwasm_2_1"} target/wasm32-unknown-unknown/release/contract.wasm: pass From 67ab1f4fe5a0fa8e1f48cf3567ae15307a6f7a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Thu, 12 Dec 2024 18:45:44 +0100 Subject: [PATCH 3/4] Sylvia tutorial fix tests --- src/pages/tutorial/sylvia-contract/entry-points.mdx | 2 +- src/pages/tutorial/sylvia-contract/first-messages.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/tutorial/sylvia-contract/entry-points.mdx b/src/pages/tutorial/sylvia-contract/entry-points.mdx index 851f842c..d5379255 100644 --- a/src/pages/tutorial/sylvia-contract/entry-points.mdx +++ b/src/pages/tutorial/sylvia-contract/entry-points.mdx @@ -30,7 +30,7 @@ to manually create them, and we can rely on a macro to do that for us. Let's add the [`entry_points`](../../sylvia/macros/entry-points) attribute macro to our contract: -```rust {3, 7} copy filename="src/contract.rs" template="sylvia/empty" +```rust {3, 7} copy filename="src/contract.rs" template="sylvia-empty" use sylvia::ctx::InstantiateCtx; use sylvia::cw_std::{Response, StdResult}; use sylvia::{contract, entry_points}; diff --git a/src/pages/tutorial/sylvia-contract/first-messages.mdx b/src/pages/tutorial/sylvia-contract/first-messages.mdx index d1338018..36bb501b 100644 --- a/src/pages/tutorial/sylvia-contract/first-messages.mdx +++ b/src/pages/tutorial/sylvia-contract/first-messages.mdx @@ -18,7 +18,7 @@ pub mod contract; Now let's create an `instantiate` method for our contract. In `src/contract.rs` -```rust copy filename="src/contract.rs" template="sylvia/empty" +```rust copy filename="src/contract.rs" template="sylvia-empty" use sylvia::cw_std::{Response, StdResult}; use sylvia::contract; use sylvia::ctx::InstantiateCtx; From 82f94115dba23b357850d51cb8a34ec550eb1304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Tue, 7 Jan 2025 15:09:55 +0100 Subject: [PATCH 4/4] Sylvia tutorial first messages review fixes --- src/pages/tutorial/setup-environment.mdx | 6 +++--- src/pages/tutorial/sylvia-contract/_meta.json | 1 + src/pages/tutorial/sylvia-contract/contract-creation.mdx | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/tutorial/setup-environment.mdx b/src/pages/tutorial/setup-environment.mdx index 95d1f6d6..f39af6e9 100644 --- a/src/pages/tutorial/setup-environment.mdx +++ b/src/pages/tutorial/setup-environment.mdx @@ -94,9 +94,9 @@ All contracts (1) passed checks! ## Macro expansion -In VSCode you can hover over a macro like [`#[contract]`](../sylvia/macros/contract), do `shift+p` -and then type: `rust analyzer: Expand macro recursively`. This will open a window with a fully -expanded macro, which you can browse. In Vim you can consider installing the +In VSCode you can hover over a macro like [`#[contract]`](../sylvia/macros/contract), press +`shift+p` and then type: `rust analyzer: Expand macro recursively`. This will open a window with a +fully expanded macro, which you can browse. In Vim you can consider installing the [rustaceanvim](https://github.com/mrcjkb/rustaceanvim) plugin. You can also use `cargo expand` tool from CLI, like this: diff --git a/src/pages/tutorial/sylvia-contract/_meta.json b/src/pages/tutorial/sylvia-contract/_meta.json index 8ce646e7..2d82bb42 100644 --- a/src/pages/tutorial/sylvia-contract/_meta.json +++ b/src/pages/tutorial/sylvia-contract/_meta.json @@ -1,4 +1,5 @@ { "contract-creation": "Contract creation", + "entry-points": "Entry points", "first-messages": "First messages" } diff --git a/src/pages/tutorial/sylvia-contract/contract-creation.mdx b/src/pages/tutorial/sylvia-contract/contract-creation.mdx index 9e89ff86..de9f9259 100644 --- a/src/pages/tutorial/sylvia-contract/contract-creation.mdx +++ b/src/pages/tutorial/sylvia-contract/contract-creation.mdx @@ -16,7 +16,7 @@ cargo generate CosmWasm/sylvia-template The [`sylvia-template`](https://github.com/CosmWasm/sylvia-template) will generate a lot of code for you. Because this tutorial aims to guide you step-by-step through the process of creating your first -contract we will omit the use of the template and show you how to create it from the start. +contract we will omit the use of the template and show you how to create it from the scratch. ## New project @@ -48,7 +48,7 @@ sylvia = "1.3.1" [documentation](https://docs.rs/sylvia/latest/sylvia/#reexports). -As you can see, I added a `crate-type` field for the library section. Generating the +As you can see, we added a `crate-type` field for the library section. Generating the [`cdylib`](https://doc.rust-lang.org/reference/linkage.html) 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