From 8bb74574fa1b3ea6aa19370b531b4674dbba8c89 Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:15:36 +0200 Subject: [PATCH 1/4] MultiTest chapters. --- docs/multi-test/addresses.md | 17 + docs/multi-test/api.md | 72 +++ docs/multi-test/app-builder.md | 429 ++++++++++++++++ docs/multi-test/app.md | 79 +++ docs/multi-test/features.md | 83 ++++ .../getting-started/_category_.json | 8 + .../getting-started/counter/_category_.json | 8 + .../getting-started/counter/counter.md | 108 ++++ .../getting-started/counter/implementation.md | 468 ++++++++++++++++++ .../getting-started/introduction.mdx | 13 + .../writing-tests/_category_.json | 8 + .../writing-tests/writing-tests.md | 306 ++++++++++++ docs/multi-test/governance.md | 199 ++++++++ 13 files changed, 1798 insertions(+) create mode 100644 docs/multi-test/addresses.md create mode 100644 docs/multi-test/api.md create mode 100644 docs/multi-test/app-builder.md create mode 100644 docs/multi-test/app.md create mode 100644 docs/multi-test/features.md create mode 100644 docs/multi-test/getting-started/_category_.json create mode 100644 docs/multi-test/getting-started/counter/_category_.json create mode 100644 docs/multi-test/getting-started/counter/counter.md create mode 100644 docs/multi-test/getting-started/counter/implementation.md create mode 100644 docs/multi-test/getting-started/introduction.mdx create mode 100644 docs/multi-test/getting-started/writing-tests/_category_.json create mode 100644 docs/multi-test/getting-started/writing-tests/writing-tests.md create mode 100644 docs/multi-test/governance.md diff --git a/docs/multi-test/addresses.md b/docs/multi-test/addresses.md new file mode 100644 index 0000000..0d28dc0 --- /dev/null +++ b/docs/multi-test/addresses.md @@ -0,0 +1,17 @@ +--- +tags: ["multitest", "addresses"] +--- + +import { Cards } from "nextra/components"; + +# Addresses + +The following chapters provide useful explanations of how addresses are managed in **`MultiTest`**. + +Click on any of the cards below to learn more about each address category. + + + + + + diff --git a/docs/multi-test/api.md b/docs/multi-test/api.md new file mode 100644 index 0000000..c34f268 --- /dev/null +++ b/docs/multi-test/api.md @@ -0,0 +1,72 @@ +--- +sidebar_position: 7 +--- + +# Api + +**MultiTest** provides three implementations of the [Api][Api] trait: + +- [`cosmwasm_std::testing::MockApi`][MockApi], +- [`cw_multi_test::MockApiBech32`][MockApiBech32], +- [`cw_multi_test::MockApiBech32m`][MockApiBech32m]. + +Additionally, [`MockApi`][MockApi], [`MockApiBech32`][MockApiBech32] and +[`MockApiBech32m`][MockApiBech32m] implement `addr_make` method, allowing for +convenient creation of user addresses in tests. You can find multiple examples of `addr_make` +method usage in the [Addresses](addresses/user-address#app) chapter. Depending on your needs, you +can use any implementation of the [Api][Api] trait in your tests by utilizing the +[`AppBuilder::with_api`](app-builder#with_api) method to create a chain simulator. + +## `Api` trait + +The table below summarizes all methods of the [Api][Api] trait with short descriptions: + +| Methods of [Api] trait | Description | +|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| [`addr_canonicalize`][addr_canonicalize] | Converts a human-readable address into its canonical binary representation. | +| [`addr_humanize`][addr_humanize] | Converts a canonical address into human-readable address. | +| [`addr_validate`][addr_validate] | Checks if the human-readable address is valid. | +| [`secp256k1_verify`][secp256k1_verify] | Verifies a message hash against a signature, with the public key of the signer, using the **secp256k1** elliptic curve digital signature algorithm. | +| [`secp256k1_recover_pubkey`][secp256k1_recover_pubkey] | Recovers a public key from a message hash and a signature in compressed form, which can be used in `secp256k1_verify` directly. | +| [`secp256r1_verify`][secp256r1_verify] | Verifies a message hash against a signature, with the public key of the signer, using the **secp256r1** elliptic curve digital signature algorithm. | +| [`secp256r1_recover_pubkey`][secp256r1_recover_pubkey] | Recovers a public key from a message hash and a signature in compressed form, which can be used in `secp256r1_verify` directly. | +| [`ed25519_verify`][ed25519_verify] | Verifies message against a signature, with the public key of the signer, using the **ed25519** elliptic curve digital signature algorithm. | +| [`ed25519_batch_verify`][ed25519_batch_verify] | Performs batch **ed25519** signature verification. | +| [`bls12_381_aggregate_g1`][bls12_381_aggregate_g1] | Adds up points (48 bytes each) of the G1 subgroup on the BLS12-381 curve. | +| [`bls12_381_aggregate_g2`][bls12_381_aggregate_g2] | Adds up points (96 bytes each) of the G2 subgroup on the BLS12-381 curve. | +| [`bls12_381_pairing_equality`][bls12_381_pairing_equality] | Checks the pairing equality of the BLS12-381 curve. | +| [`bls12_381_hash_to_g1`][bls12_381_hash_to_g1] | Takes some arbitrary data and hashes it to a point (48 bytes long) on the G1 subgroup of the BLS12-381 curve. | +| [`bls12_381_hash_to_g2`][bls12_381_hash_to_g2] | Takes some arbitrary data and hashes it to a point (96 bytes long) on the G2 subgroup of the BLS12-381 curve. | +| [`debug`][debug] | Emits a debugging message. | + +## `Api` trait implementations + +A concise comparison of different [Api][Api] trait implementations is shown in the table below: + +| | MockApi | MockApiBech32 | MockApiBech32m | +|-------------------------------------|:----------:|:-------------:|:--------------:| +| Implements [Api][Api] trait | Yes | Yes | Yes | +| Implements **`addr_make`** function | Yes | Yes | Yes | +| Address format | Bech32 | Bech32 | Bech32m | +| Default prefix (HRP) | `cosmwasm` | No | No | +| Supports custom prefixes | Yes | Yes | Yes | + +[Api]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html +[addr_canonicalize]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.addr_canonicalize +[addr_humanize]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.addr_humanize +[addr_validate]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.addr_validate +[secp256k1_verify]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.secp256k1_verify +[secp256k1_recover_pubkey]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.secp256k1_recover_pubkey +[secp256r1_verify]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.secp256r1_verify +[secp256r1_recover_pubkey]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.secp256r1_recover_pubkey +[ed25519_verify]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.ed25519_verify +[ed25519_batch_verify]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.ed25519_batch_verify +[bls12_381_aggregate_g1]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.bls12_381_aggregate_g1 +[bls12_381_aggregate_g2]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.bls12_381_aggregate_g2 +[bls12_381_pairing_equality]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.bls12_381_pairing_equality +[bls12_381_hash_to_g1]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.bls12_381_hash_to_g1 +[bls12_381_hash_to_g2]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#method.bls12_381_hash_to_g2 +[debug]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.debug +[MockApi]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockApi.html +[MockApiBech32]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32.html +[MockApiBech32m]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32m.html diff --git a/docs/multi-test/app-builder.md b/docs/multi-test/app-builder.md new file mode 100644 index 0000000..de9489f --- /dev/null +++ b/docs/multi-test/app-builder.md @@ -0,0 +1,429 @@ +--- +sidebar_position: 5 +--- + +# `AppBuilder` + +[`AppBuilder`][AppBuilder] is an implementation of the [Builder Pattern] that provides a +flexible and modular way to construct the [`App`](./app) blockchain simulator. It allows smart +contract developers to configure various components of the blockchain simulator (e.g., Bank, +Staking, Gov, IBC) individually through dedicated `with_*` methods. Each method modifies and returns +a new instance of the builder, enabling method chaining for a fluent interface. The +[`build`][build] method finalizes the construction process, ensuring that all components are +correctly initialized and integrated. + +The following sections detail all builder functions, providing specific usage examples. + +## `default` + +The simplest way to create a chain using [`AppBuilder`][AppBuilder] is by calling the +`default` method. Since [`AppBuilder`][AppBuilder] follows the principles of the +builder pattern, you need to finalize the building process by calling the [`build`][build] +method with a chain initialization callback function. When no specific chain initialization is +required you can just use the provided [`no_init`](app#no_init) callback. In the following code +example, the chain is created with default settings as described in +[Features summary](features#features-summary). + +```rust showLineNumbers {3} copy /default/ /build/ +use cw_multi_test::{no_init, AppBuilder}; + +let app = AppBuilder::default().build(no_init); + +let sender_addr = app.api().addr_make("sender"); + +assert!(sender_addr.as_str().starts_with("cosmwasm1")); +``` + +## `new` + +The constructor `new` is an equivalent of the `default` method in +[`AppBuilder`][AppBuilder]. The example below creates a chain with default settings as +described in [Features summary](features#features-summary). + +```rust showLineNumbers {3} copy /new/ /build/ +use cw_multi_test::{no_init, AppBuilder}; + +let app = AppBuilder::new().build(no_init); + +let sender_addr = app.api().addr_make("sender"); + +assert!(sender_addr.as_str().starts_with("cosmwasm1")); +``` + +## `new_custom` + +(WIP) + +## `with_api` + +The default [`Api`][Api] trait implementation used in [`AppBuilder`][AppBuilder] is +[`cosmwasm_std::testing::MockApi`][MockApi] provided by CosmWasm library. Besides other +functionalities described in detail in the [API](./api) chapter, the [`MockApi`][MockApi] +provides a function [`addr_make`][addr_make] for generating user addresses in **Bech32** +format with the `cosmwasm` prefix. An example usage is shown below. + +```rust showLineNumbers copy {7} /api()/ /addr_make/ /cosmwasm/ +use cw_multi_test::{no_init, AppBuilder}; + +// Create the chain with default Api implementation. +let app = AppBuilder::default().build(no_init); + +// Create the address using default Api. +let sender_addr = app.api().addr_make("sender"); + +// Default Api generates Bech32 addresses with prefix 'cosmwasm'. +assert_eq!( + "cosmwasm1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qlm3aqg", + sender_addr.as_str() +); +``` + +If you need to test contracts on your own chain that uses a specific Bech32 prefixes, you can easily +customize the default [`MockApi`][MockApi] behavior using the +[`AppBuilder::with_api`][with_api] method. An example of using `osmo` prefix is shown in the +following code snippet. + +```rust showLineNumbers copy {1,6} /with_api/ /osmo/ +use cosmwasm_std::testing::MockApi; +use cw_multi_test::{no_init, AppBuilder}; + +// Create the chain with customized default Api implementation. +let app = AppBuilder::default() + .with_api(MockApi::default().with_prefix("osmo")) + .build(no_init); + +// Create the address using customized Api. +let sender_addr = app.api().addr_make("sender"); + +// This customized Api generates Bech32 addresses with prefix 'osmo'. +assert_eq!( + "osmo1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qcrt3u2", + sender_addr.as_str() +); +``` + +**`MultiTest`** provides two additional implementations of the [`Api`][Api] trait: +[`MockApiBech32`][MockApiBech32] and [`MockApiBech32m`][MockApiBech32m]. You can use +them in your tests by providing their instances to [`AppBuilder::with_api`][with_api] method. + +An example of using [`MockApiBech32`][MockApiBech32] with custom prefix: + +```rust showLineNumbers copy {1,6} /with_api/ /juno/ +use cw_multi_test::MockApiBech32; +use cw_multi_test::{no_init, AppBuilder}; + +// Create the chain with Bech32 Api implementation. +let app = AppBuilder::default() + .with_api(MockApiBech32::new("juno")) + .build(no_init); + +// Create the address using Bech32 Api. +let sender_addr = app.api().addr_make("sender"); + +// This Api generates Bech32 addresses with prefix 'juno'. +assert_eq!( + "juno1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qwm56ug", + sender_addr.as_str() +); +``` + +An example of using [`MockApiBech32m`][MockApiBech32m] with custom prefix: + +```rust showLineNumbers copy {1,6} /with_api/ /juno/ +use cw_multi_test::MockApiBech32m; +use cw_multi_test::{no_init, AppBuilder}; + +// Create the chain with Bech32m Api implementation. +let app = AppBuilder::default() + .with_api(MockApiBech32m::new("juno")) + .build(no_init); + +// Create the address using Bech32m Api. +let sender_addr = app.api().addr_make("sender"); + +// This Api generates Bech32m addresses with prefix 'juno'. +assert_eq!( + "juno1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qm8yke2", + sender_addr.as_str() +); +``` + +:::tip + + More details about the available [`Api`][Api] implementations are in the [API](api) chapter. + If needed, you can provide your own implementation of the [`Api`][Api] trait and + use it in your tests by utilizing the `with_api` method of the `AppBuilder`. + +::: + +## `with_bank` + +(WIP) + +## `with_block` + +While the default block configuration is sufficient for most test cases, you can initialize the +chain with a custom [`BlockInfo`][BlockInfo] using the [`with_block`][with_block] +method provided by [`AppBuilder`][AppBuilder]. + +The following example demonstrates this use case in detail. + +```rust showLineNumbers copy {15} /with_block/ +use cosmwasm_std::{BlockInfo, Timestamp}; +use cw_multi_test::{no_init, AppBuilder}; + +// create the chain builder +let builder = AppBuilder::default(); + +// prepare the custom block +let block = BlockInfo { + height: 1, + time: Timestamp::from_seconds(1723627489), + chain_id: "starship-testnet".to_string(), +}; + +// build the chain initialized with the custom block +let app = builder.with_block(block).build(no_init); + +// get the current block properties +let block = app.block_info(); + +// now the block height is 21 +assert_eq!(1, block.height); + +// now the block timestamp is Wed Aug 14 2024 09:24:49 GMT+0000 +assert_eq!(1723627489, block.time.seconds()); + +// now the chain identifier is "starship-testnet" +assert_eq!("starship-testnet", block.chain_id); +``` + +The [`AppBuilder`][AppBuilder] is initialized with default settings in line 5. A custom block +is created in lines 8-12 and passed to the [`with_block`][with_block] method in line 15. +Since this is the only customization in this example, the blockchain construction is finalized in +the same line by calling the [`build`][build] method. + +The current block metadata is retrieved in line 18, followed by value checks: + +- line 21: the block height is now `1`, +- line 24: the block time is set to the Unix timestamp `1723627489`, representing + `Wednesday, August 14, 2024, 09:24:49 GMT`, +- line 27: the chain identifier is now `"starship-testnet"`. + +The [`with_block`][with_block] method of [`AppBuilder`][AppBuilder] can be combined +with any other `with_*` methods to configure a custom starting block. + +## `with_custom` + +(WIP) + +## `with_distribution` + +(WIP) + +## `with_gov` + +The [`with_gov`][with_gov] function allows you to customize the governance module of your +test blockchain environment. This function enables you to override the default governance module +with a custom implementation that suits your specific testing needs. Currently, **`MultiTest`** +provides two minimal implementations of the governance module that do not attempt to replicate real +blockchain behavior: [`GovAcceptingModule`][GovAcceptingModule] and +[`GovFailingModule`][GovFailingModule]. + +To use a built-in governance module that accepts all messages, initialize the chain like shown below +(line 4): + +```rust showLineNumbers copy {15} /with_gov/ /GovAcceptingModule/ +use cw_multi_test::{no_init, AppBuilder, GovAcceptingModule}; + +let app = AppBuilder::default() + .with_gov(GovAcceptingModule::new()) + .build(no_init); +``` + +When processing governance messages in your tests should always fail, initialize the chain like in +the following code snippet (line 4): + +```rust showLineNumbers copy /with_gov/ /GovFailingModule/ +use cw_multi_test::{no_init, AppBuilder, GovFailingModule}; + +let app = AppBuilder::default() + .with_gov(GovFailingModule::new()) + .build(no_init); +``` + +:::tip + +Note that [`GovFailingModule`][GovFailingModule] is the default one in `App`. + +::: + +You can find more usage examples of the built-in governance modules in the [Governance](./governance) chapter. + +## `with_ibc` + +(WIP) + +## `with_staking` + +(WIP) + +## `with_stargate` + +(WIP) + +## `with_storage` + +By default, **`MultiTest`** uses [`MockStorage`][MockStorage], which is provided by the +CosmWasm library. [`MockStorage`][MockStorage] is an in-memory storage that does not persist +data beyond the test execution. Any values stored in it during testing are lost once the test +completes. + +To use the default storage, simply initialize a chain with the default settings, as shown in the +following code snippet. After initialization, [`App`][App] provides two methods to access the +storage: [`storage`][storage] and [`storage_mut`][storage_mut]. The former is used for +reading the storage, while the latter allows both reading and writing. + +```rust showLineNumbers {4} /storage_mut()/ /storage()/ +use cosmwasm_std::Storage; +use cw_multi_test::{no_init, AppBuilder}; + +let mut app = AppBuilder::default().build(no_init); + +let key = b"key"; +let value = b"value"; + +app.storage_mut().set(key, value); + +assert_eq!(value, app.storage().get(key).unwrap().as_slice()); +``` + +You can also explicitly provide a [`MockStorage`][MockStorage] instance to the +[`with_storage`][with_storage] method of [`AppBuilder`][AppBuilder]. The example below +achieves the same result as the previous one. + +```rust showLineNumbers {1,6} /storage_mut()/ /storage()/ +use cosmwasm_std::testing::MockStorage; +use cosmwasm_std::Storage; +use cw_multi_test::{no_init, AppBuilder}; + +let mut app = AppBuilder::default() + .with_storage(MockStorage::new()) + .build(no_init); + +let key = b"key"; +let value = b"value"; + +app.storage_mut().set(key, value); + +assert_eq!(value, app.storage().get(key).unwrap().as_slice()); +``` + +Initializing a chain with a [`MockStorage`][MockStorage] instance using the +[`with_storage`][with_storage] method of [`AppBuilder`][AppBuilder] allows for +sophisticated storage initialization before the chain starts. A simplified example is shown below. +In lines 8-9 the storage is created and initialized, then passed to `with_storage` method in +line 11. The initialization code in line 9 can of course be more complex in your test case. + +```rust showLineNumbers {8,9,11} /with_storage/ +use cosmwasm_std::testing::MockStorage; +use cosmwasm_std::Storage; +use cw_multi_test::{no_init, AppBuilder}; + +let key = b"key"; +let value = b"value"; + +let mut storage = MockStorage::new(); +storage.set(key, value); + +let app = AppBuilder::default().with_storage(storage).build(no_init); + +assert_eq!(value, app.storage().get(key).unwrap().as_slice()); +``` + +It is worth noting that the same initialization can be implemented directly in a callback function +passed to [`build`][build] method of [`AppBuilder`][AppBuilder] as shown in the +following example: + +```rust showLineNumbers {11} /build/ +use cosmwasm_std::testing::MockStorage; +use cosmwasm_std::Storage; +use cw_multi_test::AppBuilder; + +let key = b"key"; +let value = b"value"; + +let app = AppBuilder::default() + .with_storage(MockStorage::new()) + .build(|router, api, storage| { + storage.set(key, value); + }); + +assert_eq!(value, app.storage().get(key).unwrap().as_slice()); +``` + +:::tip + +In the examples above, to keep the simple, we accessed the storage directly and focused on the chain +initialization part involving the `with_storage` method of `AppBuilder`. +Please note, that inside smart contract code, the storage used by the chain should be accessed through +libraries like [StoragePlus](../storage-plus). + +::: + +You can find additional information about storage in the [Storage](./storage) chapter. + +## `with_wasm` + +(WIP) + +## `build` + +Since [`AppBuilder`][AppBuilder] follows the principles of the builder pattern, you must +always finalize the chain-building process by calling the [`build`][build] method with an +initialization callback function. If no specific chain initialization is required, you can use the +provided [`no_init`](app#no_init) callback. Otherwise, you can initialize the chain using a custom +callback function. An example of how to initialize a user's balance is shown below. + +```rust showLineNumbers copy /build/ +use cosmwasm_std::coin; +use cw_multi_test::AppBuilder; + +let my_address = "me".into_addr(); +let my_funds = vec![coin(23, "ATOM"), coin(18, "FLOCK")]; + +let app = AppBuilder::default().build(|router, api, storage| { + router + .bank + .init_balance(storage, &my_address, my_funds) + .unwrap(); +}); + +assert_eq!( + "23ATOM", + app.wrap() + .query_balance(my_address, "ATOM") + .unwrap() + .to_string() +); +``` + +[Api]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html +[App]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html +[AppBuilder]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html +[build]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.build +[Builder Pattern]: https://en.wikipedia.org/wiki/Builder_pattern +[BlockInfo]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.BlockInfo.html +[GovAcceptingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.GovAcceptingModule.html +[GovFailingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.GovFailingModule.html +[MockApi]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockApi.html +[MockApiBech32]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32.html +[MockApiBech32m]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32m.html +[with_api]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.with_api +[with_block]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.with_block +[with_gov]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.with_gov +[with_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.with_storage +[addr_make]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockApi.html#method.addr_make +[MockStorage]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockStorage.html +[storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.storage +[storage_mut]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.storage_mut diff --git a/docs/multi-test/app.md b/docs/multi-test/app.md new file mode 100644 index 0000000..2c074b5 --- /dev/null +++ b/docs/multi-test/app.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 4 +--- + + +# `App` + +The [`App`][App] structure represents the blockchain simulator. Creating [`App`][App] +mimics the startup of a real-life blockchain within tests that utilize **MultiTest**. + +## `default` + +The most straightforward way to create an [`App`][App] is by using the `default` method, + +```rust showLineNumbers +use cw_multi_test::App; + +let app = App::default(); +``` + +which is an equivalent of calling the `new` constructor with an empty initialization callback [`no_init`][no_init]. + +```rust showLineNumbers +use cw_multi_test::{no_init, App}; + +let app = App::new(no_init); +``` + +In both cases, the newly created [`App`][App] object simulates a blockchain with the default +settings as summarized in the chapter [Features summary](features#features-summary). + +## `new` + +Using the [`App::new`][new] constructor enables additional initialization of the blockchain +at startup, prior to executing any tests. Initialization is done inside the callback function passed +as an argument to the `new` constructor (lines 7-13 in the code snippet shown below). + +Initialization callback function takes three arguments: + +- **router** - provides access to other modules of the blockchain, +- **api** - provides access to CosmWasm API, +- **storage** - provides access to the blockchain's storage. + +An example of funds initialization inside the custom callback function is shown below: + +```rust showLineNumbers {7-13} +use cosmwasm_std::coin; +use cw_multi_test::App; + +let me = "me"; +let my_funds = vec![coin(20, "OSMO")]; + +let app = App::new(|router, api, storage| { + let my_address = api.addr_make(me); + router + .bank + .init_balance(storage, &my_address, my_funds) + .unwrap(); +}); + +let my_coins = app + .wrap() + .query_all_balances(app.api().addr_make(me)) + .unwrap(); + +assert_eq!(1, my_coins.len()); +assert_eq!("20OSMO", my_coins[0].to_string()); +``` + +## `no_init` + +The [`no_init`][no_init] function serves as an empty chain initialization callback, +offering a convenient option when no specific chain initialization is required. +Usually used when calling [`App::new`][new] or [`AppBuilder::build`][build] methods. + +[App]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html +[new]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.new +[build]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.build +[no_init]: https://docs.rs/cw-multi-test/latest/cw_multi_test/fn.no_init.html diff --git a/docs/multi-test/features.md b/docs/multi-test/features.md new file mode 100644 index 0000000..88d0069 --- /dev/null +++ b/docs/multi-test/features.md @@ -0,0 +1,83 @@ +--- +sidebar_position: 2 +--- + +# Features + +## Features summary + +All **MultiTest** features are listed in the table below. + +The **Default implementation** column indicates whether the feature has a default +implementation in **MultiTest** that simulates the functionality of the real blockchain as closely +as possible. In cases where **MultiTest** does not have a default implementation for the feature, +you can provide your own, using `AppBuilder`'s function listed in **AppBuilder constructor** +column. Names of **MultiTest** feature flags required to enable specific functionality are shown +in the column **Feature flag**. + +| Feature | Default
implementation | Feature
flag | AppBuilder
constructor | Functionality | +|----------------|---------------------------------------------------|:----------------:|------------------------------------------------------|----------------------------------------------------| +| [Blocks] | [`mock_env().block`][mock_env_block] | | [`with_block`](app-builder#with_block) | Operations on blocks. | +| [Api] | [`MockApi`][MockApi] | | [`with_api`](app-builder#with_api) | Access to CosmWasm API. | +| [Storage] | [`MockStorage`][MockStorage] | | [`with_storage`](app-builder#with_storage) | Access to storage. | +| [Bank] | [`BankKeeper`][BankKeeper] | | [`with_bank`](app-builder#with_bank) | Interactions with **Bank** module. | +| [Staking] | [`StakeKeeper`][StakeKeeper] | `staking` | [`with_staking`](app-builder#with_staking) | Interactions with **Staking** module. | +| [Distribution] | [`DistributionKeeper`][DistributionKeeper] | `staking` | [`with_distribution`](app-builder#with_distribution) | Interactions with **Distribution** module. | +| [Governance] | [`GovFailingModule`][GovFailingModule] | | [`with_gov`](app-builder#with_gov) | Interactions with **Governance** module. | +| [Stargate] | [`StargateFailing`][StargateFailing] | `stargate` | [`with_stargate`](app-builder#with_stargate) | Operations using `Stargate` and/or `Any` messages. | +| [Wasm] | [`WasmKeeper`][WasmKeeper] | | [`with_wasm`](app-builder#with_wasm) | Interactions with **Wasm** module. | +| [Custom] | [`FailingModule`][FailingModule] | | [`new_custom`](app-builder#new_custom) | Operations using custom module. | +| [IBC] | [`IbcFailingModule`][IbcFailingModule] | `stargate` | [`with_ibc`](app-builder#with_ibc) | Inter-blockchain communication operations. | + +## Feature flags summary + +The following table summarizes feature flags supported by **MultiTest**. + +| Feature flag | Description | +|----------------|-------------------------------------------------------------------------------------------------------------------------------------| +| `backtrace` | Enables `backtrace` feature in _**anyhow**_ dependency. | +| `staking` | Enables `staking` feature in _**cosmwasm-std**_ dependency and enables staking/distribution functionality in **MultiTest** library. | +| `stargate` | Enables `stargate` feature in _**cosmwasm-std**_ dependency and enables stargate/IBC functionality in **MultiTest** library. | +| `cosmwasm_1_1` | Enables `cosmwasm_1_1` feature in _**cosmwasm-std**_ dependency. | +| `cosmwasm_1_2` | Enables `cosmwasm_1_2` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_1_1` feature in **MultiTest** library. | +| `cosmwasm_1_3` | Enables `cosmwasm_1_3` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_1_2` feature in **MultiTest** library. | +| `cosmwasm_1_4` | Enables `cosmwasm_1_4` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_1_3` feature in **MultiTest** library. | +| `cosmwasm_2_0` | Enables `cosmwasm_2_0` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_1_4` feature in **MultiTest** library. | +| `cosmwasm_2_1` | Enables `cosmwasm_2_1` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_2_0` feature in **MultiTest** library. | +| `cosmwasm_2_2` | Enables `cosmwasm_2_2` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_2_1` feature in **MultiTest** library. | +| `cosmwasm_3_0` | Enables `cosmwasm_3_0` feature in _**cosmwasm-std**_ dependency and additionally `cosmwasm_2_2` feature in **MultiTest** library. | + +## Starting point + +Usually, a good starting point when using **MultiTest** is the following dependency configuration in the **Cargo.toml** file: + +```toml title="Cargo.toml" +[dependencies] +cosmwasm-std = "3" + +[dev-dependencies] +cw-multi-test = { version = "3", features = ["staking", "stargate", "cosmwasm_3_0"] } +``` + +[Blocks]: ./introduction.md +[Api]: ./introduction.md +[Storage]: ./introduction.md +[Bank]: ./introduction.md +[Staking]: ./introduction.md +[Distribution]: ./introduction.md +[Governance]: ./introduction.md +[Stargate]: ./introduction.md +[Wasm]: ./introduction.md +[Custom]: ./introduction.md +[IBC]: ./introduction.md +[mock_env_block]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/fn.mock_env.html +[MockApi]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockApi.html +[MockStorage]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockStorage.html +[BankKeeper]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.BankKeeper.html +[StakeKeeper]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.StakeKeeper.html +[DistributionKeeper]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.DistributionKeeper.html +[GovFailingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.GovFailingModule.html +[StargateFailing]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.StargateFailing.html +[WasmKeeper]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.WasmKeeper.html +[FailingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.FailingModule.html +[IbcFailingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.IbcFailingModule.html diff --git a/docs/multi-test/getting-started/_category_.json b/docs/multi-test/getting-started/_category_.json new file mode 100644 index 0000000..8aa5499 --- /dev/null +++ b/docs/multi-test/getting-started/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Getting started", + "position": 3, + "link": { + "type": "doc", + "id": "introduction" + } +} diff --git a/docs/multi-test/getting-started/counter/_category_.json b/docs/multi-test/getting-started/counter/_category_.json new file mode 100644 index 0000000..1d399e3 --- /dev/null +++ b/docs/multi-test/getting-started/counter/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Counter", + "position": 1, + "link": { + "type": "doc", + "id": "counter" + } +} diff --git a/docs/multi-test/getting-started/counter/counter.md b/docs/multi-test/getting-started/counter/counter.md new file mode 100644 index 0000000..69634ac --- /dev/null +++ b/docs/multi-test/getting-started/counter/counter.md @@ -0,0 +1,108 @@ +# Counter + +The following sections describe how to create an example smart contract, +which manages a **counter** that can be `initialized`, `incremented`, `decremented`, and `queried`. +Later on, you will be [writing tests](writing-tests.mdx) for this smart contract using **MultiTest**. + +:::info + +**Counter** smart contract is a simplified version of the contract created from the +[cw-template](https://github.com/CosmWasm/cw-template). +Check [Setting up the contract](../../../core/installation#setting-up-the-contract) for more details. + +::: + +## Specification + +The example smart contract is a simple **counter** that allows users to initialize the counter, +perform operations such as incrementing, decrementing, setting a specific value, and querying the +current counter value. The primary purpose of this contract is to maintain and manipulate a counter +value on the blockchain. This functionality can be utilized in various scenarios where a count or +tally needs to be tracked, such as counting votes, tracking the number of actions performed, +or maintaining a score in a game or competition. + +**Features:** + +- The counter can be initialized with a value of zero or any non-negative integer between 0 and 255. +- Users can increment, decrement, or set the counter to a new value within the range of 0 to 255. +- Boundary conditions are checked, preventing the counter from going below 0 or beyond 255. +- Users can query the current value of the counter at any time. +- The counter's value is persisted and limited to 8-bit unsigned integers [0..255]. + +## Creating the counter project + +Smart contracts written in Rust are developed as Rust libraries, so let's first create a Rust +library named **counter**. + +Change the working directory to your home directory: + +```shell +cd ~ +``` + +Create a dedicated directory to store your example smart contract: + +```shell +mkdir my-contracts +``` + +Change the working directory to `my-contracts`: + +```shell +cd my-contracts +``` + +Create a new Rust library named **counter**: + +```shell +cargo init --lib counter +``` + +Change the working directory to `counter`: + +```shell +cd counter +``` + +Newly created library contains the **Cargo.toml** file and **lib.rs** file in `src` directory, +so the expected structure of the `counter` directory is: + +```text title="counter directory" +. +├── Cargo.toml +└── src + └── lib.rs +``` + +By convention, the source code of the smart contract is placed in a file named **contract.rs**, and +the messages processed by this contract are usually placed in file named **msg.rs**. Both files +should be stored in `src` directory. + +Let's create an empty **contract.rs** file... + +```shell +touch src/contract.rs +``` + +...and empty **msg.rs** file: + +```shell +touch src/msg.rs +``` + +The final structure of the smart contract project placed in the `counter` directory should look like this: + +```text title="counter directory" +. +├── Cargo.toml +└── src + ├── contract.rs + ├── lib.rs + └── msg.rs +``` + +## Filling the content + +In the previous section you have created a project structure for **counter** smart contract, +but the source files are still empty. In the following chapter, we provide an example +[**implementation**](./implementation.md) of the **counter** smart contract. diff --git a/docs/multi-test/getting-started/counter/implementation.md b/docs/multi-test/getting-started/counter/implementation.md new file mode 100644 index 0000000..f818176 --- /dev/null +++ b/docs/multi-test/getting-started/counter/implementation.md @@ -0,0 +1,468 @@ +--- +title: Implementation +sidebar_position: 1 +--- + +# Counter implementation + +The following code snippets present the content of [Cargo.toml](#cargotoml), [lib.rs](#librs), +[msg.rs](#msgrs), and [contract.rs](#contractrs) files, respectively. +You can just copy and paste the provided content to previously created empty files, temporarily +skipping the detailed explanations. However, if you're curious about what happens inside each file, +feel free to check the detailed explanations provided for each code snippet. + +## Cargo.toml + +```toml title="Cargo.toml" showLineNumbers +[package] +name = "counter" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = "2" +cosmwasm-std = { version = "2", features = ["cosmwasm_2_2"] } +cw-storage-plus = "2" +schemars = "0.8" +serde = "1.0" + +[dev-dependencies] +cw-multi-test = { version = "2", features = ["cosmwasm_2_2"] } +``` + +`Cargo.toml` file is a configuration file for a Rust project, in our case for a smart contract +written in Rust. Here's a detailed explanation of each section and what it's doing. + +### \[package\] section + +```toml title="Cargo.toml" showLineNumbers +[package] +name = "counter" +version = "0.1.0" +edition = "2021" +``` + +- **`[package]`** section provides metadata about the Rust crate (smart contract library in our + case). +- **`name = "counter"`** specifies the name of the crate, it's named **counter** like our smart + contract. +- **`version = "0.1.0"`** indicates the current version of the package and the counter smart + contract. +- **`edition = "2021"`** specifies the Rust edition being used; editions in Rust are sets of + language and compiler improvements, with 2021 being one of the latest editions at the time, + providing the latest features and enhancements. + +### \[lib\] section + +```toml title="Cargo.toml" showLineNumbers=8 +[lib] +crate-type = ["cdylib", "rlib"] +``` + +- **`[lib]`** section specifies settings for building the library. +- **`crate-type`** enumerates types of libraries to be produced during compiling. +- **`"cdylib"`** specifies that the package will be compiled as a C-compatible dynamic library; + which is required for smart contracts to run on the CosmWasm runtime. +- **`"rlib"`** specifies a Rust library file that can be used as a dependency for other Rust + projects, in our case for other smart contracts. + +### \[features\] section + +```toml title="Cargo.toml" showLineNumbers=11 +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] +``` + +- **`[features]`** section defines optional features for the Rust package. +- **`library = []`** defines a feature named **library**, which when set, disables exporting smart + contract entry-points. Exporting entry points is necessary for interacting with the smart contract + on the blockchain. However, when the contract is used as a dependency by other contracts, + exporting these entry-points should be disabled to prevent unintended function name clashes. + +### \[dependencies\] section + +```toml title="Cargo.toml" showLineNumbers=15 +[dependencies] +cosmwasm-schema = "2" +cosmwasm-std = { version = "2", features = ["cosmwasm_2_2"] } +cw-storage-plus = "2" +schemars = "0.8" +serde = "1.0" +``` + +- **`[dependencies]`** section lists the libraries that the package depends on. +- **`cosmwasm-schema`** is used for generating JSON schemas from Rust data structures, which is + useful for documentation and ensuring compatibility of messages and queries. +- **`cosmwasm-std`** is the standard library for CosmWasm contracts, providing common types and + utilities needed for interacting with the CosmWasm runtime. +- **`cw-storage-plus`** is a library that provides advanced storage abstractions and utilities on + top of the basic storage capabilities in CosmWasm, making it easier to manage state within contracts. +- **`schemars`** is a library for generating JSON schemas, which complements `cosmwasm-schema` by + providing additional features for schema generation. +- **`serde`** is a widely used serialization library in Rust, allowing easy conversion of Rust data + structures to and from formats like JSON, which is crucial for data interchange in smart contracts. + +### \[dev-dependencies\] section + +```toml title="Cargo.toml" showLineNumbers=22 +[dev-dependencies] +cw-multi-test = { version = "2", features = ["cosmwasm_2_2"] } +``` + +- **`[dev-dependencies]`** section lists dependencies that are only needed for development and + testing. +- **`cw-multi-test`** is a name of **`MultiTest`** library, and should **ALWAYS** be placed in + **[dev-dependencies]** section. + +Overall, this `Cargo.toml` file configures a Rust project for a CosmWasm-based smart contract. It +sets up the basic package details, specifies how the contract should be compiled, defines +dependencies for core functionality and testing, and includes features to enable or disable certain +parts of the contract code. This setup ensures the contract can be developed, tested, and deployed +effectively on the blockchain within the CosmWasm ecosystem. + +## lib.rs + +```rust title="lib.rs" showLineNumbers +pub mod contract; +pub mod msg; +``` + +The `lib.rs` file in a Rust project serves as the main entry point for defining the structure of a +library. In the context of our example **counter** smart contract, the `lib.rs` file is defining and +organizing the modules that make up the contract. Recall the **counter** project file structure: + +```text {4,6} title="counter directory content" +. +├── Cargo.toml +└── src + ├── contract.rs + ├── lib.rs + └── msg.rs +``` + +There are two modules in the project: `contract.rs` and `msg.rs`. That's why in the `lib.rs` file +there are two declarations: + +- `pub mod contract;`
This line declares a public module named **contract**; tells Rust + to include the code from a file named `contract.rs` located in the same directory and makes the + module publicly accessible (`pub` keyword), which means that other modules or external code can + access the entry-points of our smart contract. +- `pub mod msg;`
This line declares a public module named **msg**; includes the code + from a file named `msg.rs` and also makes this module public which allows other parts of the code + (especially our **counter** smart contract) to access the messages defined here. + +Overall, this `lib.rs` file is setting up the main structure of the smart contract by defining its +key components as separate modules. This organization helps in keeping the code clean, modular, and +maintainable by separating the core contract logic (`contract.rs`) from the message and query +definitions (`msg.rs`). This modular approach makes the smart contract easier to understand, extend, +and test. + +## msg.rs + +The **msg** module in file `msg.rs` typically defines the messages and queries that the smart +contract accepts and responds to. Messages are usually structured as Rust enums or structs and +define the input and output interfaces of the contract. In our example this includes messages shown +below. + +```rust title="msg.rs" showLineNumbers +use cosmwasm_schema::{cw_serde, QueryResponses}; + +#[cw_serde] +pub enum CounterInitMsg { + Zero, + Set(u8), +} + +#[cw_serde] +pub enum CounterExecMsg { + Inc, + Dec, + Set(u8), +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum CounterQueryMsg { + #[returns(CounterResponse)] + Value, +} + +#[cw_serde] +pub struct CounterResponse { + pub value: u8, +} +``` + +Let's take a detailed look at the implementation of these messages. + +### Imports + +```rust title="msg.rs" showLineNumbers +use cosmwasm_schema::cw_serde; +``` + +Required imports, like `cw_serde` annotation. + +### Instantiation message + +This message is passed to `instantiate` entry-point. + +```rust title="msg.rs" showLineNumbers=3 +#[cw_serde] +pub enum CounterInitMsg { + Zero, + Set(u8), +} +``` + +`CounterInitMsg` enumeration is used to initialize the contract. +`CounterInitMsg::Zero` variant initializes the counter with the zero value, +and `CounterInitMsg::Set` variant initializes the counter with an arbitrary value in range 0 to 255. +This message is passed to `instantiate` entry-point of the counter smart contract. + +### Execution message + +This message is passed to `execute` entry-point. + +```rust title="msg.rs" showLineNumbers=9 +#[cw_serde] +pub enum CounterExecMsg { + Inc, + Dec, + Set(u8), +} +``` + +`CounterExecMsg` enumeration is used to perform various actions within the contract, +especially incrementing (the `CounterExecMsg::Inc` variant), decrementing (the `CounterExecMsg::Dec` variant) +and setting an arbitrary counter value (the `CounterExecMsg::Set` variant). +This message is passed to `execute` entry-point of the counter smart contract. + +### Query message + +This message is passed to `query` entry-point. + +```rust title="msg.rs" showLineNumbers=16 +#[cw_serde] +#[derive(QueryResponses)] +pub enum CounterQueryMsg { + #[returns(CounterResponse)] + Value, +} +``` + +`CounterQueryMsg` enumeration, with its single variant `CounterQueryMsg::Value` is used to query +the state of the contract, in our case to retrieve the current counter value. This message is passed +to `query` entry-point of the counter smart contract. The `#[derive(QueryResponses)]` annotation informs +the schema generator about the type of the value returned by the query. + +### Response message + +This message is returned from `query` entry-point and passed to the user. + +```rust title="msg.rs" showLineNumbers=21 +#[cw_serde] +pub struct CounterResponse { + pub value: u8, +} +``` + +`CounterResponse` struct with a single field `value`, used to pass the responses (results) from the queries. + +Overall, the `msg.rs` file is basically setting up the contract’s _social skills_, defining how it +interacts with the outside world by initializing, executing actions, and answering questions. Each +message type is a different way of communicating with the counter, making it an easy-going, +versatile smart contract, ready for action. + +## contract.rs + +Typically, in a smart contract project, the **contract** module (placed in the `contract.rs` file) +contains the core logic of the contract, including functions (entry-points) for instantiation, +execution, querying and migrating. This is where the main functionality of the smart contract is +implemented. And this is also the case for our **counter** smart contract. The full source code is +shown below. + +```rust title="contract.rs" showLineNumbers +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; + +use crate::msg::{CounterExecMsg, CounterInitMsg, CounterQueryMsg, CounterResponse}; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError}; +use cw_storage_plus::Item; + +const COUNTER: Item = Item::new("value"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterInitMsg, +) -> Result { + COUNTER.save( + deps.storage, + &match msg { + CounterInitMsg::Zero => 0, + CounterInitMsg::Set(new_value) => new_value, + }, + )?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterExecMsg, +) -> Result { + COUNTER.update::<_, StdError>(deps.storage, |old_value| { + Ok(match msg { + CounterExecMsg::Inc => old_value.saturating_add(1), + CounterExecMsg::Dec => old_value.saturating_sub(1), + CounterExecMsg::Set(new_value) => new_value, + }) + })?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result { + match msg { + CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse { + value: COUNTER.may_load(deps.storage)?.unwrap(), + })?), + } +} +``` + +Let's take a detailed look at this implementation. + +### Conditional imports + +```rust title="contract.rs" showLineNumbers=1 +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +``` + +The import shown above is used for conditional compilation in CosmWasm smart contracts. It ensures +that the `entry_point` annotation is only included when the contract is being compiled as a +standalone WASM binary, not as a library. The `entry_point` annotation is essential for defining the +main functions of the smart contract, such as instantiate, execute, and query, which are responsible +for interacting with the contract. If you're using this smart contract in another project, this +configuration ensures that unnecessary code isn't included when compiling it as a library. + +### Imports + +```rust title="contract.rs" showLineNumbers=4 +use crate::msg::{CounterExecMsg, CounterInitMsg, CounterQueryMsg, CounterResponse}; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError}; +use cw_storage_plus::Item; +``` + +These are additional imports required to compile the contract. The first `use` statement +imports message structures for handling execution, initialization, and queries related to a +counter-based smart contract. The second `use` imports essential tools and types from the +CosmWasm standard library, required for interacting with the blockchain environment, converting +data, and accessing dependencies. The third `use` brings in the `Item` type, which is +needed for storing single values in the contract’s persistent storage. + +### Storage variable definition + +```rust title="contract.rs" showLineNumbers=8 +const COUNTER: Item = Item::new("value"); +``` + +`COUNTER` is a storage variable (although declared as `const`) for a counter, +represented as an 8-bit unsigned integer. The `"value"` string is the key used to store and +retrieve the counter value in the contract's persistent storage. This is used for tracking a counter +value that can be incremented, decremented, queried, or reset by the smart contract. More details +about the [Item](../../../storage-plus/containers/item.md) type can be found in +[cw-storage-plus](../../../storage-plus) documentation. + +### _instantiate_ entrypoint + +The `instantiate` function (entry-point) is called during the instantiation of the smart +contract. Depending on the value of the message passed in `msg` argument, the counter will be +initialized with zero or with the value provided in `CounterInitMsg::Set` variant. Using the +`COUNTER` variable, the initial value is saved in the contract's persistent storage for +future use. + +```rust title="contract.rs" showLineNumbers=10 +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterInitMsg, +) -> Result { + COUNTER.save( + deps.storage, + &match msg { + CounterInitMsg::Zero => 0, + CounterInitMsg::Set(new_value) => new_value, + }, + )?; + Ok(Response::default()) +} +``` + +### _execute_ entrypoint + +The `execute` function (entry-point) is called whenever the user wants to interact with the +contract, especially when the value of the counter should be incremented, decremented or reset. +Depending on the `msg` value passed as an argument, the "old value" of the counter will be +incremented by one for `CounterExecMsg::Inc` variant, decremented by one for +`CounterExecMsg::Dec` variant, or replaced with the new value for +`CounterExecMsg::Set` variant. The new value will be saved in the contract's persistent +storage by calling `update` function of the `COUNTER` variable. + +```rust title="contract.rs" showLineNumbers=27 +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterExecMsg, +) -> Result { + COUNTER.update::<_, StdError>(deps.storage, |old_value| { + Ok(match msg { + CounterExecMsg::Inc => old_value.saturating_add(1), + CounterExecMsg::Dec => old_value.saturating_sub(1), + CounterExecMsg::Set(new_value) => new_value, + }) + })?; + Ok(Response::default()) +} +``` + +### _query_ entrypoint + +The `query` function (entry-point) is called whenever the user asks the counter smart +contract for the current value. The counter value is retrieved from the contract's persistent +storage, wrapped with `CounterResponse` type and returned to the user. + +```rust title="contract.rs" showLineNumbers=44 +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result { + match msg { + CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse { + value: COUNTER.may_load(deps.storage)?.unwrap(), + })?), + } +} +``` + +## What next? + +Having the **counter** smart contract prepared, you can begin writing tests using **MultiTest**! diff --git a/docs/multi-test/getting-started/introduction.mdx b/docs/multi-test/getting-started/introduction.mdx new file mode 100644 index 0000000..d1901de --- /dev/null +++ b/docs/multi-test/getting-started/introduction.mdx @@ -0,0 +1,13 @@ +# Getting started + +This unit is designed to help you quickly become familiar with the fundamental aspects of testing +smart contracts using **MultiTest**. It provides a practical example and best practices to ensure +a smooth start with using **MultiTest** for testing smart contracts. +In the following chapters, you will be: + +- designing an example [**counter**](./counter) smart contract, +- [**writing tests**](./counter) for the counter smart contract. + +The example **counter** smart contract and all test cases are provided in two versions: one using +pure CosmWasm libraries and the other using the Sylvia framework. The functionality of the +**counter** smart contract is the same in both versions. diff --git a/docs/multi-test/getting-started/writing-tests/_category_.json b/docs/multi-test/getting-started/writing-tests/_category_.json new file mode 100644 index 0000000..7d6b28f --- /dev/null +++ b/docs/multi-test/getting-started/writing-tests/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Writing tests", + "position": 2, + "link": { + "type": "doc", + "id": "writing-tests" + } +} diff --git a/docs/multi-test/getting-started/writing-tests/writing-tests.md b/docs/multi-test/getting-started/writing-tests/writing-tests.md new file mode 100644 index 0000000..be7e320 --- /dev/null +++ b/docs/multi-test/getting-started/writing-tests/writing-tests.md @@ -0,0 +1,306 @@ +# Writing tests + +Having the **counter** smart contract set up, let's first check if the project compiles: + +```shell +cargo build +``` + +```text title="output" + Updating crates.io index +  Locking 112 packages to latest compatible versions +    ⋮ +Compiling cosmwasm-crypto v2.1.3 +Compiling cosmwasm-std v2.1.3 +Compiling cw-storage-plus v2.0.0 +Compiling counter v0.1.0 (/home/user/my-contracts/counter) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.27s +``` + +If the output is similar to the one shown above, it looks like the **counter** smart contract has +been built successfully. + +It is a very good habit to run [Rust linter](https://doc.rust-lang.org/clippy) after each code +change, so let's run it before we move forward. + +```shell +cargo clippy +``` + +```text title="output" +    ⋮ +Checking cosmwasm-crypto v2.1.3 +Checking cosmwasm-std v2.1.3 +Checking cw-storage-plus v2.0.0 +Checking counter v0.1.0 (/home/user/mt-test-examples/mte-counter) +Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.28s +``` + +Luckily, `clippy` reports no issues for the **counter** smart contract. + +## Preparing the directory structure for tests + +Before we start writing tests, we need to set up the directories and files for the test cases. The +final directory and file structure are shown below. + +```ansi showLineNumbers {7-11} filename="counter (directory)" +. +├── Cargo.toml +├── src +│   ├── contract.rs +│   ├── lib.rs +│   └── msg.rs +└── tests +    ├── mod.rs +    └── multitest +        ├── mod.rs +        └── test_counter.rs +``` + +:::tip + +There are several configurations possible for placing tests in Rust. For the purpose of this example, +we have chosen to place all test cases outside **`src`** directory, in a new directory named **`tests`**. + +::: + +:::tip + +Note, that both directories **`src`** and **`tests`** are placed at the root of the project (in the `counter` directory). + +::: + +Let's begin by creating the `tests` directory: + +```shell copy filename="TERMINAL" +mkdir tests +``` + +Then create an empty `mod.rs` file inside the `tests` directory: + +```shell copy filename="TERMINAL" +touch tests/mod.rs +``` + +Now, copy and paste the following content to `tests/mod.rs` file: + +```rust copy filename="tests/mod.rs" +mod multitest; +``` + +By convention, we place all **`MultiTest`** test cases under the `multitest` directory, so let's +create it: + +```shell copy filename="TERMINAL" +mkdir tests/multitest +``` + +Inside the `tests/multitest` directory we should also create an empty file named `mod.rs`: + +```shell copy filename="TERMINAL" +touch tests/multitest/mod.rs +``` + +And populate it with the following content (just copy and paste it): + +```rust copy filename="tests/multitest/mod.rs" +mod test_counter; +``` + +Finally, inside the `tests/multitest` directory, we create a file named `test_counter.rs`: + +```shell copy filename="TERMINAL" +touch tests/multitest/test_counter.rs +``` + +For now, we will leave this file empty, but later, we will place all our test cases there. + +Now that the directory structure for tests is ready, it's time to run all tests. + +## Running all tests for counter + +Once the directories and files are set up for tests, let's execute them: + +```shell copy filename="TERMINAL" +cargo test +``` + +The expected output should be similar to the one shown below: + +```ansi {6,12,16} showLineNumbers filename="OUTPUT" + Finished `test` profile [unoptimized + debuginfo] target(s) in 17.96s + Running unittests src/lib.rs (target/debug/deps/counter-f350df45a1cd1c74) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running tests/mod.rs (target/debug/deps/mod-54761c1d31e6d0fe) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests counter + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` + +Rust testing framework searches for several types of tests in the project, counts all test cases and +executes them. In our example - while we haven't written any single test yet - there is nothing to +execute. The reported number of executed tests is **0** for unit tests (line 2), **0** for +integration tests (line 12) and **0** for documentation tests (line 16). + +Similarly, to execute all tests using [cargo-nextest](https://nexte.st), type: + +```shell copy filename="TERMINAL" +cargo nextest run +``` + +The expected output is: + +```ansi {4} showLineNumbers filename="OUTPUT" + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06s + Starting 0 tests across 2 binaries (run ID: 3e0cbabb-3ef9-4b2f-98a8-d375bc510845, nextest profile: default) +------------ + Summary [ 0.000s] 0 tests run: 0 passed, 0 skipped +``` + +[cargo-nextest](https://nexte.st) reports in a user-friendly manner that there were no tests to run (**line 4**). + +Now, you have almost everything you need to start testing the **counter** smart contract. What’s left is to prepare +the tools for measuring code coverage, which is a handy way to track progress in writing tests. + +## Preparing the code coverage script + +Typing the entire command for Tarpaulin every time you want to measure current code coverage can be +quite tedious, so let's prepare a short script to automate this task. + +Create an empty file named `coverage.sh` in the `counter` directory: + +```shell copy filename="TERMINAL" +touch coverage.sh +``` + +Populate it with the following content: + +```shell copy filename="coverage.sh" +#!/usr/bin/env bash + +# generate coverage report +cargo tarpaulin --force-clean --out Html --engine llvm --output-dir "$(pwd)/target/coverage-report" + +# display link to coverage report +echo "Report: file://$(pwd)/target/coverage-report/tarpaulin-report.html" +``` + +Finally, make this file executable: + +```shell filename="TERMINAL" +chmod +x coverage.sh +``` + +The complete file structure of the **counter** smart contract project, with the code coverage script +should now look like this: + +```shell filename="TERMINAL" +tree +``` + +```ansi {3} filename="counter (directory)" +. +├── Cargo.toml +├── coverage.sh +├── src +│   ├── contract.rs +│   ├── lib.rs +│   └── msg.rs +└── tests +    ├── mod.rs +    └── multitest +        ├── mod.rs +        └── test_counter.rs +``` + +## Measuring code coverage + +With the code coverage script at hand, measuring code coverage is now as simple as typing: + +```shell copy filename="TERMINAL" +./coverage.sh +``` + +The result should be similar to this (only the last few lines are shown): + +```ansi filename="OUTPUT" +⋮ +|| Tested/Total Lines: +|| src/contract.rs: 0/18 +|| +0.00% coverage, 0/18 lines covered +Report: file:///home/user/counter/target/coverage-report/tarpaulin-report.html +``` + +Additionally, Tarpaulin generates a coverage report in HTML format, that can be viewed directly in a browser. +As expected, the current code coverage for the **counter** smart contract is **0.00%** since we haven't written +any tests yet. Follow the next chapters, and make the code coverage report shine green. + +```rust title="Code coverage report" showLineNumbers {11-25,28-42,45-51} +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; + +use crate::msg::{CounterExecMsg, CounterInitMsg, CounterQueryMsg, CounterResponse}; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError}; +use cw_storage_plus::Item; + +const COUNTER: Item = Item::new("value"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterInitMsg, +) -> Result { + COUNTER.save( + deps.storage, + &match msg { + CounterInitMsg::Zero => 0, + CounterInitMsg::Set(new_value) => new_value, + }, + )?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: CounterExecMsg, +) -> Result { + COUNTER.update::<_, StdError>(deps.storage, |old_value| { + Ok(match msg { + CounterExecMsg::Inc => old_value.saturating_add(1), + CounterExecMsg::Dec => old_value.saturating_sub(1), + CounterExecMsg::Set(new_value) => new_value, + }) + })?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result { + match msg { + CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse { + value: COUNTER.may_load(deps.storage)?.unwrap(), + })?), + } +} +``` + +## Writing tests for smart contracts + +Now it is time to start implementing tests for the **counter** smart contract. diff --git a/docs/multi-test/governance.md b/docs/multi-test/governance.md new file mode 100644 index 0000000..eff09ca --- /dev/null +++ b/docs/multi-test/governance.md @@ -0,0 +1,199 @@ +--- +sidebar_position: 13 +--- + +# Governance + +**`MultiTest`** provides two minimal implementations of the governance module that **do not** +replicate the real blockchain behavior: [`GovAcceptingModule`][GovAcceptingModule] and +[`GovFailingModule`][GovFailingModule]. The default chain configuration is using +[`GovFailingModule`][GovFailingModule]. + +The following sections provide examples of how to initialize a specific governance module and how to +execute governance messages in tests. + +## Default governance module + +Currently, in **`MultiTest`**, the default governance module is +[`GovFailingModule`][GovFailingModule], and no additional configuration is required to use +it. + +### `GovMsg::Vote` + +```rust showLineNumbers {5,20} copy /GovMsg::Vote/ +use cosmwasm_std::{GovMsg, VoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, IntoAddr}; + +// Build the application with default (always failing) governance module. +let mut app = AppBuilder::default().build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for vote. +let vote_msg = GovMsg::Vote { + proposal_id: 1, + option: VoteOption::Yes, +}; + +// Execute vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap_err(); + +// Always an error is returned. +assert!(response.to_string().starts_with("Unexpected exec msg Vote")); +``` + +### `GovMsg::VoteWeighted` + +```rust showLineNumbers {5,23} copy /GovMsg::VoteWeighted/ +use cosmwasm_std::{Decimal, GovMsg, Uint128, VoteOption, WeightedVoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, IntoAddr}; + +// Build the application with default (always failing) governance module. +let mut app = AppBuilder::default().build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for weighted vote. +let vote_msg = GovMsg::VoteWeighted { + proposal_id: 1, + options: vec![WeightedVoteOption { + option: VoteOption::Yes, + weight: Decimal::new(Uint128::new(12)), + }], +}; + +// Execute weighted vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap_err(); + +// Always an error is returned. +assert!(response.to_string().starts_with("Unexpected exec msg VoteWeighted")); +``` + +## Failing governance module + +Executing governance messages using [`GovFailingModule`][GovFailingModule] always returns an +error. + +### `GovMsg::Vote` + +```rust showLineNumbers {6,22} copy /GovMsg::Vote/ +use cosmwasm_std::{GovMsg, VoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, GovFailingModule, IntoAddr}; + +// Build the application with always failing governance module. +let mut app = AppBuilder::default() + .with_gov(GovFailingModule::new()) + .build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for vote. +let vote_msg = GovMsg::Vote { + proposal_id: 1, + option: VoteOption::Yes, +}; + +// Execute vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap_err(); + +// Always an error is returned. +assert!(response.to_string().starts_with("Unexpected exec msg Vote")); +``` + +### `GovMsg::VoteWeighted` + +```rust showLineNumbers {6,25} copy /GovMsg::VoteWeighted/ +use cosmwasm_std::{Decimal, GovMsg, Uint128, VoteOption, WeightedVoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, GovFailingModule, IntoAddr}; + +// Build the application with always failing governance module. +let mut app = AppBuilder::default() + .with_gov(GovFailingModule::new()) + .build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for weighted vote. +let vote_msg = GovMsg::VoteWeighted { + proposal_id: 1, + options: vec![WeightedVoteOption { + option: VoteOption::Yes, + weight: Decimal::new(Uint128::new(12)), + }], +}; + +// Execute weighted vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap_err(); + +// Always an error is returned. +assert!(response.to_string().starts_with("Unexpected exec msg VoteWeighted")); +``` + +## Accepting governance module + +Executing governance messages using [`GovAcceptingModule`][GovAcceptingModule] always +succeeds, but the returned data is empty. + +### `GovMsg::Vote` + +```rust showLineNumbers {6,22} copy /GovMsg::Vote/ +use cosmwasm_std::{GovMsg, VoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, GovAcceptingModule, IntoAddr}; + +// Build the application with always failing governance module. +let mut app = AppBuilder::default() + .with_gov(GovAcceptingModule::new()) + .build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for vote. +let vote_msg = GovMsg::Vote { + proposal_id: 1, + option: VoteOption::Yes, +}; + +// Execute vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap(); + +// Always empty data is returned. +assert_eq!(None, response.data); +``` + +### `GovMsg::VoteWeighted` + +```rust showLineNumbers {6,25} copy /GovMsg::VoteWeighted/ +use cosmwasm_std::{Decimal, GovMsg, Uint128, VoteOption, WeightedVoteOption}; +use cw_multi_test::{no_init, AppBuilder, Executor, GovAcceptingModule, IntoAddr}; + +// Build the application with always failing governance module. +let mut app = AppBuilder::default() + .with_gov(GovAcceptingModule::new()) + .build(no_init); + +// Prepare sender address. +let sender_addr = "sender".into_addr(); + +// Prepare message for weighted vote. +let vote_msg = GovMsg::VoteWeighted { + proposal_id: 1, + options: vec![WeightedVoteOption { + option: VoteOption::Yes, + weight: Decimal::new(Uint128::new(12)), + }], +}; + +// Execute weighted vote governance message. +let response = app.execute(sender_addr, vote_msg.into()).unwrap(); + +// Always empty data is returned. +assert_eq!(None, response.data); +``` + +[GovAcceptingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.GovAcceptingModule.html +[GovFailingModule]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.GovFailingModule.html From bc800ad3529428d4c28b33edfc126eb477c135da Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:37:59 +0200 Subject: [PATCH 2/4] Added more content. --- docs/multi-test/addresses.md | 17 - docs/multi-test/addresses/_category_.json | 8 + docs/multi-test/addresses/addresses.md | 7 + docs/multi-test/addresses/contract-address.md | 7 + docs/multi-test/addresses/user-address.md | 407 ++++++++++++++++++ .../multi-test/addresses/validator-address.md | 7 + docs/multi-test/app-builder.md | 2 +- .../getting-started/counter/_category_.json | 2 +- .../getting-started/counter/implementation.md | 2 +- .../counter/{counter.md => introduction.md} | 2 +- .../{introduction.mdx => introduction.md} | 4 +- .../writing-tests/_category_.json | 2 +- .../{writing-tests.md => introduction.md} | 0 docs/multi-test/storage.md | 218 ++++++++++ 14 files changed, 661 insertions(+), 24 deletions(-) delete mode 100644 docs/multi-test/addresses.md create mode 100644 docs/multi-test/addresses/_category_.json create mode 100644 docs/multi-test/addresses/addresses.md create mode 100644 docs/multi-test/addresses/contract-address.md create mode 100644 docs/multi-test/addresses/user-address.md create mode 100644 docs/multi-test/addresses/validator-address.md rename docs/multi-test/getting-started/counter/{counter.md => introduction.md} (96%) rename docs/multi-test/getting-started/{introduction.mdx => introduction.md} (77%) rename docs/multi-test/getting-started/writing-tests/{writing-tests.md => introduction.md} (100%) create mode 100644 docs/multi-test/storage.md diff --git a/docs/multi-test/addresses.md b/docs/multi-test/addresses.md deleted file mode 100644 index 0d28dc0..0000000 --- a/docs/multi-test/addresses.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -tags: ["multitest", "addresses"] ---- - -import { Cards } from "nextra/components"; - -# Addresses - -The following chapters provide useful explanations of how addresses are managed in **`MultiTest`**. - -Click on any of the cards below to learn more about each address category. - - - - - - diff --git a/docs/multi-test/addresses/_category_.json b/docs/multi-test/addresses/_category_.json new file mode 100644 index 0000000..8e460ab --- /dev/null +++ b/docs/multi-test/addresses/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Addresses", + "position": 8, + "link": { + "type": "doc", + "id": "addresses" + } +} diff --git a/docs/multi-test/addresses/addresses.md b/docs/multi-test/addresses/addresses.md new file mode 100644 index 0000000..839ae9f --- /dev/null +++ b/docs/multi-test/addresses/addresses.md @@ -0,0 +1,7 @@ +# Addresses + +The following chapters provide useful explanations of how addresses are managed in **MultiTest**: + +- [User address](./user-address.md) +- [Contract address](./contract-address.md) +- [Validator address](./validator-address.md) diff --git a/docs/multi-test/addresses/contract-address.md b/docs/multi-test/addresses/contract-address.md new file mode 100644 index 0000000..b02e70b --- /dev/null +++ b/docs/multi-test/addresses/contract-address.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 2 +--- + +# Contract address + +(WIP) diff --git a/docs/multi-test/addresses/user-address.md b/docs/multi-test/addresses/user-address.md new file mode 100644 index 0000000..d029701 --- /dev/null +++ b/docs/multi-test/addresses/user-address.md @@ -0,0 +1,407 @@ +--- +sidebar_position: 1 +--- + +# User address + +In the real-life blockchain based on Cosmos-SDK, user addresses are derived from the public key of +the user in the following manner: + +- the public key of the user (in raw bytes) is first hashed using SHA-256, +- then the resulting SHA-256 hash is hashed again using RIPEMD-160; this step reduces the hash to 20 + bytes, which is the standard length of addresses in Cosmos-SDK, +- finally the resulting RIPEMD-160 hash is then encoded using Bech32 format; a human-readable prefix + (HRP) specific to the blockchain is prepended. + +In **MultiTest**, user addresses are indeed derived from a user-provided string, such as an alias +(e.g., **alice**, **creator**, etc.) and not from an actual public/private key pair, which +simplifies address creation for testing purposes in two steps: + +- the alias or any provided string is first hashed using SHA-256, +- and then the 32-byte hash is encoded using Bech32 format; a human-readable prefix (HRP) specific + to the blockchain is prepended. + +The following examples demonstrate (in several ways) how to generate a Bech32 addresses (optionally +with custom prefix) using these common approaches: + +- the `addr_make` function, +- the `IntoAddr`, `IntoBech32` and `IntoBech32m` traits, +- the `MockApi`, `MockApiBech32` and `MockApiBech32m` structs. + +## App + +> Calling `addr_make` on `app.api()` + +Having the chain simulator `App` already created, you can just call `addr_make` +function, providing any string as an argument, like shown below in line 5. The address returned by +this function is by default encoded in **Bech32** format and has a default prefix **cosmwasm**. +Notice, that the `App` is created with default settings (line 3): + +```rust showLineNumbers {3,5} copy /addr_make/ +use cw_multi_test::App; + +let app = App::default(); + +let addr = app.api().addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +); +``` + +Exactly the same address will be generated when the `App` is created using the default +settings in `AppBuilder`, like shown below in lines 3 and 5: + +```rust showLineNumbers {3,5} copy /addr_make/ +use cw_multi_test::AppBuilder; + +let app = AppBuilder::default().build(no_init); + +let addr = app.api().addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +); +``` + +The next two code snippets demonstrate how to customize the `App` using `AppBuilder` +to generate addresses with the custom prefix, just like in your blockchain. For this purpose, the +[`MockApiBech32`][MockApiBech32] and [`MockApiBech32m`][MockApiBech32m] structs can be +used along with `with_api` method of `AppBuilder`. + +If the address should be encoded in **Bech32** format then use +[`MockApiBech32`][MockApiBech32] to configure the `AppBuilder` like shown in line 4, +and then call the `addr_make` function (line 7) exactly the same way as in the previous +examples: + +```rust showLineNumbers {4,7} copy /addr_make/ /MockApiBech32::new/ +use cw_multi_test::{no_init, AppBuilder, MockApiBech32}; + +let app = AppBuilder::default() + .with_api(MockApiBech32::new("nebula")) + .build(no_init); + +let addr = app.api().addr_make("owner"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() +); +``` + +When **Bech32m** is the required address encoding, then just use +[`MockApiBech32m`][MockApiBech32m] to configure the `AppBuilder`: + +```rust showLineNumbers {4,7} copy /addr_make/ /MockApiBech32m::new/ +use cw_multi_test::{no_init, AppBuilder, MockApiBech32m}; + +let app = AppBuilder::default() + .with_api(MockApiBech32m::new("nebula")) + .build(no_init); + +let addr = app.api().addr_make("owner"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsevs0fr", + addr.as_str() +); +``` + +The last example demonstrates how to generate addresses encoded in **Bech32m** format with the +default human-readable prefix **cosmwasm**: + +```rust showLineNumbers {4,7} copy /addr_make/ /MockApiBech32m::new/ +use cw_multi_test::{no_init, AppBuilder, MockApiBech32m}; + +let app = AppBuilder::default() + .with_api(MockApiBech32m::new("cosmwasm")) + .build(no_init); + +let addr = app.api().addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsl5lc5x", + addr.as_str() +); +``` + +## IntoAddr + +> 🖝 Using [`IntoAddr`][IntoAddr] trait + +The most generic and future-proof way to generate a user address is by using +[`IntoAddr`][IntoAddr] trait. Internally, this trait uses [`MockApi`][MockApi] struct +which currently generates addresses encoded in **Bech32** format with the default prefix +**cosmwasm**. Should the format or prefix change in the future, this trait will generate addresses +adjusted to these changes. To convert any string to the address, just call `into_addr` method +on it, like shown in line 3: + +```rust showLineNumbers {3} copy /into_addr/ +use cw_multi_test::IntoAddr; + +let addr = "owner".into_addr(); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +) +``` + +Similarly, when you need an address with the prefix to be the same as in your blockchain, then call +`into_addr_with_prefix` method on any string, like in line 3 shown below: + +```rust showLineNumbers {3} copy /into_addr_with_prefix/ +use cw_multi_test::IntoAddr; + +let addr = "owner".into_addr_with_prefix("nebula"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() +); +``` + +## IntoBech32 + +> 🖝 Using [`IntoBech32`][IntoBech32] trait + +When you want to stick to **Bech32** format in tests, then always use +[`IntoBech32`][IntoBech32] trait. To generate an address with the default prefix +**cosmwasm**, just call `into_bech32` method on any string like in line 3: + +```rust showLineNumbers {3} copy /into_bech32/ +use cw_multi_test::IntoBech32; + +let addr = "owner".into_bech32(); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +); +``` + +Custom prefix can be used by calling `into_bech32_with_prefix` method on the string: + +```rust showLineNumbers {3} copy /into_bech32_with_prefix/ +use cw_multi_test::IntoBech32; + +let addr = "owner".into_bech32_with_prefix("nebula"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() +) +``` + +## IntoBech32m + +> 🖝 Using [`IntoBech32m`][IntoBech32m] trait + +In case you would need **Bech32m** format while testing your contracts, then use +[`IntoBech32m`][IntoBech32m] trait. An address with the default prefix **cosmwasm** can be +generated by calling `into_bech32m` method on any string (line 3): + +```rust showLineNumbers {3} copy /into_bech32m/ +use cw_multi_test::IntoBech32m; + +let addr = "owner".into_bech32m(); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsl5lc5x", + addr.as_str() +); +``` + +Similarly, the custom prefix can be provided to method `into_bech32m_with_prefix` called on +string: + +```rust showLineNumbers {3} copy /into_bech32m_with_prefix/ +use cw_multi_test::IntoBech32m; + +let addr = "owner".into_bech32m_with_prefix("nebula"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsevs0fr", + addr.as_str() +); +``` + +:::tip + +All the methods of generating user addresses described above are built on the functionality +provided by the [`MockApi`][MockApi], [`MockApiBech32`][MockApiBech32] and +[`MockApiBech32m`][MockApiBech32m] structs. In certain scenarios, you might find +it useful to use these structs directly - for instance, when creating utility functions +for tests or custom testing frameworks. Examples of their usage are provided below. +::: + +## MockApi + +> 🖝 Using [`MockApi`][MockApi] struct + +To generate an address with default prefix **cosmwasm** encoded in **Bech32** format use method +`addr_make` on default instance of [`MockApi`][MockApi]: + +```rust showLineNumbers {3} copy /addr_make/ +use cosmwasm_std::testing::MockApi; + +let addr = MockApi::default().addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +); +``` + +The `with_prefix` method applies a custom prefix and **Bech32** encoding to generated +address: + +```rust showLineNumbers {3} copy /with_prefix/ /addr_make/ +use cosmwasm_std::testing::MockApi; + +let addr = MockApi::default().with_prefix("nebula").addr_make("owner"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() +); +``` + +## MockApiBech32 + +> 🖝 Using [`MockApiBech32`][MockApiBech32] struct + +**cosmwasm** prefix and **Bech32** encoding: + +```rust showLineNumbers {3} copy /MockApiBech32::new/ /addr_make/ +use cw_multi_test::MockApiBech32; + +let addr = MockApiBech32::new("cosmwasm").addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() +); +``` + +Custom prefix and **Bech32** encoding: + +```rust showLineNumbers {3} copy /MockApiBech32::new/ /addr_make/ +use cw_multi_test::MockApiBech32; + +let addr = MockApiBech32::new("nebula").addr_make("owner"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() +); +``` + +## MockApiBech32m + +> 🖝 Using [`MockApiBech32m`][MockApiBech32m] struct + +**cosmwasm** prefix and **Bech32m** encoding: + +```rust showLineNumbers {3} copy /MockApiBech32m::new/ /addr_make/ +use cw_multi_test::MockApiBech32m; + +let addr = MockApiBech32m::new("cosmwasm").addr_make("owner"); + +assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsl5lc5x", + addr.as_str() +); +``` + +Custom prefix and **Bech32m** encoding: + +```rust showLineNumbers {3} copy /MockApiBech32m::new/ /addr_make/ +use cw_multi_test::MockApiBech32m; + +let addr = MockApiBech32m::new("nebula").addr_make("owner"); + +assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsevs0fr", + addr.as_str() +); +``` + +## Initialization callback + +During blockchain initialization, a common use case is supplying user accounts with tokens before +running tests. In these scenarios, user addresses can be generated directly within the +initialization callback function, passed as an argument to the `App::new` or +`AppBuilder::build` methods. Examples of such usage are provided below and are +self-explanatory. + +Default **cosmwasm** prefix with **Bech32** encoding: + +```rust {4} showLineNumbers copy /addr_make/ / api,/ +use cw_multi_test::App; + +let app = App::new(|router, api, storage| { + let addr = api.addr_make("owner"); + assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y", + addr.as_str() + ); +}); +``` + +Custom prefix with **Bech32** encoding: + +```rust {6} showLineNumbers copy /addr_make/ / api,/ +use cw_multi_test::{AppBuilder, MockApiBech32}; + +let app = AppBuilder::default() + .with_api(MockApiBech32::new("nebula")) + .build(|router, api, storage| { + let addr = api.addr_make("owner"); + assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsvsqrvp", + addr.as_str() + ); + }); +``` + +Custom prefix with **Bech32m** encoding: + +```rust {6} showLineNumbers copy /addr_make/ / api,/ +use cw_multi_test::{AppBuilder, MockApiBech32m}; + +let app = AppBuilder::default() + .with_api(MockApiBech32m::new("nebula")) + .build(|router, api, storage| { + let addr = api.addr_make("owner"); + assert_eq!( + "nebula1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsevs0fr", + addr.as_str() + ); + }); +``` + +**cosmwasm** prefix with **Bech32m** encoding: + +```rust {6} showLineNumbers copy /addr_make/ / api,/ +use cw_multi_test::{AppBuilder, MockApiBech32m}; + +let app = AppBuilder::default() + .with_api(MockApiBech32m::new("cosmwasm")) + .build(|router, api, storage| { + let addr = api.addr_make("owner"); + assert_eq!( + "cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqsl5lc5x", + addr.as_str() + ); + }); +``` + +[MockApi]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockApi.html +[MockApiBech32]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32.html +[MockApiBech32m]: https://docs.rs/cw-multi-test/latest/cw_multi_test/type.MockApiBech32m.html +[IntoAddr]: https://docs.rs/cw-multi-test/latest/cw_multi_test/trait.IntoAddr.html +[IntoBech32]: https://docs.rs/cw-multi-test/latest/cw_multi_test/trait.IntoBech32.html +[IntoBech32m]: https://docs.rs/cw-multi-test/latest/cw_multi_test/trait.IntoBech32m.html diff --git a/docs/multi-test/addresses/validator-address.md b/docs/multi-test/addresses/validator-address.md new file mode 100644 index 0000000..027dc1d --- /dev/null +++ b/docs/multi-test/addresses/validator-address.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 3 +--- + +# Validator address + +(WIP) diff --git a/docs/multi-test/app-builder.md b/docs/multi-test/app-builder.md index de9489f..589960b 100644 --- a/docs/multi-test/app-builder.md +++ b/docs/multi-test/app-builder.md @@ -367,7 +367,7 @@ assert_eq!(value, app.storage().get(key).unwrap().as_slice()); In the examples above, to keep the simple, we accessed the storage directly and focused on the chain initialization part involving the `with_storage` method of `AppBuilder`. Please note, that inside smart contract code, the storage used by the chain should be accessed through -libraries like [StoragePlus](../storage-plus). +libraries like [StoragePlus](../storage-plus/introduction.md). ::: diff --git a/docs/multi-test/getting-started/counter/_category_.json b/docs/multi-test/getting-started/counter/_category_.json index 1d399e3..069dd37 100644 --- a/docs/multi-test/getting-started/counter/_category_.json +++ b/docs/multi-test/getting-started/counter/_category_.json @@ -3,6 +3,6 @@ "position": 1, "link": { "type": "doc", - "id": "counter" + "id": "introduction" } } diff --git a/docs/multi-test/getting-started/counter/implementation.md b/docs/multi-test/getting-started/counter/implementation.md index f818176..ac2ec00 100644 --- a/docs/multi-test/getting-started/counter/implementation.md +++ b/docs/multi-test/getting-started/counter/implementation.md @@ -388,7 +388,7 @@ represented as an 8-bit unsigned integer. The `"value"` string is the key used t retrieve the counter value in the contract's persistent storage. This is used for tracking a counter value that can be incremented, decremented, queried, or reset by the smart contract. More details about the [Item](../../../storage-plus/containers/item.md) type can be found in -[cw-storage-plus](../../../storage-plus) documentation. +[StoragePlus](../../../storage-plus/introduction.md) documentation. ### _instantiate_ entrypoint diff --git a/docs/multi-test/getting-started/counter/counter.md b/docs/multi-test/getting-started/counter/introduction.md similarity index 96% rename from docs/multi-test/getting-started/counter/counter.md rename to docs/multi-test/getting-started/counter/introduction.md index 69634ac..4249a90 100644 --- a/docs/multi-test/getting-started/counter/counter.md +++ b/docs/multi-test/getting-started/counter/introduction.md @@ -2,7 +2,7 @@ The following sections describe how to create an example smart contract, which manages a **counter** that can be `initialized`, `incremented`, `decremented`, and `queried`. -Later on, you will be [writing tests](writing-tests.mdx) for this smart contract using **MultiTest**. +Later on, you will be [writing tests](../writing-tests/introduction.md) for this smart contract using **MultiTest**. :::info diff --git a/docs/multi-test/getting-started/introduction.mdx b/docs/multi-test/getting-started/introduction.md similarity index 77% rename from docs/multi-test/getting-started/introduction.mdx rename to docs/multi-test/getting-started/introduction.md index d1901de..f1d942f 100644 --- a/docs/multi-test/getting-started/introduction.mdx +++ b/docs/multi-test/getting-started/introduction.md @@ -5,8 +5,8 @@ smart contracts using **MultiTest**. It provides a practical example and best pr a smooth start with using **MultiTest** for testing smart contracts. In the following chapters, you will be: -- designing an example [**counter**](./counter) smart contract, -- [**writing tests**](./counter) for the counter smart contract. +- designing an example [**counter**](./counter/introduction.md) smart contract, +- [**writing tests**](./writing-tests/introduction.md) for the counter smart contract. The example **counter** smart contract and all test cases are provided in two versions: one using pure CosmWasm libraries and the other using the Sylvia framework. The functionality of the diff --git a/docs/multi-test/getting-started/writing-tests/_category_.json b/docs/multi-test/getting-started/writing-tests/_category_.json index 7d6b28f..632d160 100644 --- a/docs/multi-test/getting-started/writing-tests/_category_.json +++ b/docs/multi-test/getting-started/writing-tests/_category_.json @@ -3,6 +3,6 @@ "position": 2, "link": { "type": "doc", - "id": "writing-tests" + "id": "introduction" } } diff --git a/docs/multi-test/getting-started/writing-tests/writing-tests.md b/docs/multi-test/getting-started/writing-tests/introduction.md similarity index 100% rename from docs/multi-test/getting-started/writing-tests/writing-tests.md rename to docs/multi-test/getting-started/writing-tests/introduction.md diff --git a/docs/multi-test/storage.md b/docs/multi-test/storage.md new file mode 100644 index 0000000..29eb681 --- /dev/null +++ b/docs/multi-test/storage.md @@ -0,0 +1,218 @@ +--- +sidebar_position: 9 +--- + +# Storage + +## Default storage + +By default, **`MultiTest`** relies on [`MockStorage`][MockStorage], a storage implementation +provided by the CosmWasm library. [`MockStorage`][MockStorage] operates entirely in memory, +meaning that any data written during testing is discarded once the test completes. Since it does not +persist any data beyond the test execution, each test is executed in isolation without retaining any +previous state. To use a default storage in your tests, just create the chain with default settings, +as shown below. + +```rust showLineNumbers +// initialize your chain this way: +let app = App::default(); + +// or this way: +let app = AppBuilder::default().build(no_init); +``` + +The [`App`][App] provides several methods to access and manipulate the storage, all of which +are covered in the [Accessing storage in tests](#accessing-storage-in-tests) section. + +## Custom storage + +If the default storage does not fully meet your testing requirements, you can provide a custom +storage by implementing the [Storage] trait. Only the `get`, `set`,`remove` and +`range` methods are required, as the trait already provides a basic implementation for +`range_keys` and `range_values` methods. The table below summarizes all these methods. + +| Methods of [Storage] trait | Description | +|--------------------------------|---------------------------------------------------------------------------| +| [`get`][get] | Returns a value associated with a specified key. | +| [`set`][set] | Sets a new value for a specified key. | +| [`remove`][remove] | Removes an entry with a specified key. | +| [`range`][range] | Iterates over a set of **key/value** pairs, either forwards or backwards. | +| [`range_keys`][range_keys] | Iterates over a set of **keys**, either forwards or backwards. | +| [`range_values`][range_values] | Iterates over a set of **values**, either forwards or backwards. | + +For inspiration on implementing custom storage, you can refer to [MemoryStorage] in the CosmWasm +library. The following code stub could be a good starting point. + +```rust showLineNumbers copy +#[derive(Default)] +struct CustomStorage(/* use your persistent type here */); + +impl Storage for CustomStorage { + fn get(&self, key: &[u8]) -> Option> { + // return a value associated with the specified key + } + + fn set(&mut self, key: &[u8], value: &[u8]) { + // associate value with specified key and persist them + } + + fn remove(&mut self, key: &[u8]) { + // remove an entry with specified key from storage + } + + fn range<'a>(&'a self, start: Option<&[u8]>, end: Option<&[u8]>, order: Order) -> Box + 'a> { + // return an iterator over key/value pairs + } +} +``` + +## Initializing storage + +To initialize the default storage or use a custom storage implementation in tests, use the +[`AppBuilder::with_storage`][with_storage] method when building the chain. For more details, +see the [AppBuilder/with_storage](app-builder#with_storage) chapter. + +## Accessing storage from smart contracts + +Smart contracts should access the storage used by the chain through libraries like [StoragePlus](../storage-plus/introduction.md). + +## Accessing storage in tests + +[App] provides several methods to access and manipulate storage in tests, and the table below +summarizes them all. + +| Methods of [App] | Access | Purpose | +|--------------------------------------------------------------------------|:------:|----------------------------------------------------------------------------------------------| +| [`storage`][app_storage] | R | Read the value associated with any key. | +| [`storage_mut`][app_storage_mut] | R/W | Read or write the value associated with any key. | +| [`contract_storage`][app_contract_storage] | R | Read the value associated with any key but only for specified the contract address. | +| [`contract_storage_mut`][app_contract_storage_mut] | R/W | Read or write the value associated with any key but only for specified the contract address. | +| [`prefixed_storage`][app_prefixed_storage] | R | Read the value associated with any key having a single prefix. | +| [`prefixed_storage_mut`][app_prefixed_storage_mut] | R/W | Read or write the value associated with any key having a single prefix. | +| [`prefixed_multilevel_storage`][app_prefixed_multilevel_storage] | R | Read the value associated with any key having multiple prefixes. | +| [`prefixed_multilevel_storage_mut`][app_prefixed_multilevel_storage_mut] | R/W | Read or write the value associated with any key having a multiple prefixes. | + +- `R` - read-only +- `R/W` - read/write + +### `storage` + +Using methods [`storage`][app_storage] and [`storage_mut`][app_storage_mut] you can +access any value associated with a key, as long as you know the key (with or without a prefix). The +following example firstly assigns value to a key in line 9, and then reads the value from storage in +line 13. The key can be any binary. + +```rust showLineNumbers copy /storage_mut/ /storage/ +use cosmwasm_std::Storage; +use cw_multi_test::App; + +let mut app = App::default(); + +let key = b"key"; +let value = b"value"; + +app.storage_mut().set(key, value); + +assert_eq!( + Some(value.to_vec()), + app.storage().get(key) +); +``` + +### `contract_storage` + +If you know the address of a contract, you can access all keys and values stored by that contract +using the [`contract_storage`][app_contract_storage] and +[`contract_storage_mut`][app_contract_storage_mut] methods. The following example assigns a +value to a key in a similar way to how the contract would (line 11) and then reads this value on +line 15. These methods are giving easy and simple access to the storage of any contract. + +```rust showLineNumbers copy /contract_storage_mut/ /contract_storage/ +use cw_multi_test::App; +use cw_multi_test::IntoAddr; + +let mut app = App::default(); + +let key = b"key"; +let value = b"value"; + +let contract_addr = "contract".into_addr(); + +app.contract_storage_mut(&contract_addr).set(key, value); + +assert_eq!( + Some(value.to_vec()), + app.contract_storage(&contract_addr).get(key) +); +``` + +### `prefixed_storage` + +Methods [`prefixed_storage`][app_prefixed_storage] and +[`prefixed_storage_mut`][app_prefixed_storage_mut] simplify access to keys having the same +single prefix. In the example below, the common prefix (namespace) is the bank. Value is set in line +10 and then retrieved in line 14. + +```rust showLineNumbers copy /prefixed_storage_mut/ /prefixed_storage/ +use cw_multi_test::App; + +let mut app = App::default(); + +let key = b"key"; +let value = b"value"; + +let namespace = b"bank"; + +app.prefixed_storage_mut(namespace).set(key, value); + +assert_eq!( + Some(value.to_vec()), + app.prefixed_storage(namespace).get(key) +); +``` + +### `prefixed_multilevel_storage` + +If there is a need to access keys having multiple namespaces (multilevel prefixes), then methods +[`prefixed_multilevel_storage`][app_prefixed_multilevel_storage] and +[`prefixed_multilevel_storage_mut`][app_prefixed_multilevel_storage_mut] should be used. The +following example sets a value for a key having two levels of prefixes (line 10) and then the same +value is read in line 14. + +```rust showLineNumbers copy /prefixed_multilevel_storage_mut/ /prefixed_multilevel_storage/ +use cw_multi_test::App; + +let mut app = App::default(); + +let key = b"key"; +let value = b"value"; + +let namespaces = &[b"my-module".as_slice(), b"my-bank".as_slice()]; + +app.prefixed_multilevel_storage_mut(namespaces).set(key, value); + +assert_eq!( + Some(value.to_vec()), + app.prefixed_multilevel_storage(namespaces).get(key) +); +``` + +[MockStorage]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/testing/struct.MockStorage.html +[MemoryStorage]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.MemoryStorage.html +[Storage]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html +[App]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html +[get]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#tymethod.get +[set]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#tymethod.set +[remove]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#tymethod.remove +[range]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#method.range +[range_keys]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#method.range_keys +[range_values]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html#method.range_values +[with_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.AppBuilder.html#method.with_storage +[app_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.storage +[app_storage_mut]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.storage_mut +[app_contract_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.contract_storage +[app_contract_storage_mut]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.contract_storage_mut +[app_prefixed_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.prefixed_storage +[app_prefixed_storage_mut]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.prefixed_storage_mut +[app_prefixed_multilevel_storage]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.prefixed_multilevel_storage +[app_prefixed_multilevel_storage_mut]: https://docs.rs/cw-multi-test/latest/cw_multi_test/struct.App.html#method.prefixed_multilevel_storage_mut From d3717bb335086f82b189e7c8d0b9e02395cd7c8e Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:46:37 +0200 Subject: [PATCH 3/4] Updates. --- src/css/custom.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index 28ed869..054faf7 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -8,7 +8,7 @@ --ifm-color-primary-lightest: #c9baff; --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --docusaurus-highlighted-code-line-bg: rgba(229, 240, 252, 1); --ifm-footer-background-color: transparent; .footer { @@ -25,7 +25,7 @@ --ifm-color-primary-lighter: #c0aeff; --ifm-color-primary-lightest: #ebe6ff; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --docusaurus-highlighted-code-line-bg: rgba(229, 240, 252, 0.1); --ifm-footer-background-color: transparent; .footer { From b208c15e382a9072dc4f72fb93b4ab01a067369c Mon Sep 17 00:00:00 2001 From: Dariusz Depta <141360751+DariuszDepta@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:19:54 +0200 Subject: [PATCH 4/4] Added coverage hightlighting, removed ascii control chcracters. --- .../writing-tests/introduction.md | 147 +++++++++--------- docusaurus.config.ts | 17 ++ src/css/custom.css | 8 + 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/docs/multi-test/getting-started/writing-tests/introduction.md b/docs/multi-test/getting-started/writing-tests/introduction.md index be7e320..27c285f 100644 --- a/docs/multi-test/getting-started/writing-tests/introduction.md +++ b/docs/multi-test/getting-started/writing-tests/introduction.md @@ -7,14 +7,14 @@ cargo build ``` ```text title="output" - Updating crates.io index -  Locking 112 packages to latest compatible versions -    ⋮ -Compiling cosmwasm-crypto v2.1.3 -Compiling cosmwasm-std v2.1.3 -Compiling cw-storage-plus v2.0.0 -Compiling counter v0.1.0 (/home/user/my-contracts/counter) - Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.27s + Updating crates.io index + Locking 112 packages to latest compatible versions + ⋮ +Compiling cosmwasm-crypto v2.1.3 +Compiling cosmwasm-std v2.1.3 +Compiling cw-storage-plus v2.0.0 +Compiling counter v0.1.0 (/home/user/my-contracts/counter) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.27s ``` If the output is similar to the one shown above, it looks like the **counter** smart contract has @@ -28,12 +28,12 @@ cargo clippy ``` ```text title="output" -    ⋮ -Checking cosmwasm-crypto v2.1.3 -Checking cosmwasm-std v2.1.3 -Checking cw-storage-plus v2.0.0 -Checking counter v0.1.0 (/home/user/mt-test-examples/mte-counter) -Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.28s + ⋮ +Checking cosmwasm-crypto v2.1.3 +Checking cosmwasm-std v2.1.3 +Checking cw-storage-plus v2.0.0 +Checking counter v0.1.0 (/home/user/mt-test-examples/mte-counter) +Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.28s ``` Luckily, `clippy` reports no issues for the **counter** smart contract. @@ -43,18 +43,18 @@ Luckily, `clippy` reports no issues for the **counter** smart contract. Before we start writing tests, we need to set up the directories and files for the test cases. The final directory and file structure are shown below. -```ansi showLineNumbers {7-11} filename="counter (directory)" -. -├── Cargo.toml -├── src -│   ├── contract.rs -│   ├── lib.rs -│   └── msg.rs -└── tests -    ├── mod.rs -    └── multitest -        ├── mod.rs -        └── test_counter.rs +```text title="counter directory" showLineNumbers {7-11} +. +├── Cargo.toml +├── src +│ ├── contract.rs +│ ├── lib.rs +│ └── msg.rs +└── tests + ├── mod.rs + └── multitest + ├── mod.rs + └── test_counter.rs ``` :::tip @@ -72,44 +72,44 @@ Note, that both directories **`src`** and **`tests`** are placed at the root of Let's begin by creating the `tests` directory: -```shell copy filename="TERMINAL" +```shell mkdir tests ``` Then create an empty `mod.rs` file inside the `tests` directory: -```shell copy filename="TERMINAL" +```shell touch tests/mod.rs ``` Now, copy and paste the following content to `tests/mod.rs` file: -```rust copy filename="tests/mod.rs" +```rust title="tests/mod.rs" mod multitest; ``` -By convention, we place all **`MultiTest`** test cases under the `multitest` directory, so let's +By convention, we place all **MultiTest** test cases under the `multitest` directory, so let's create it: -```shell copy filename="TERMINAL" +```shell mkdir tests/multitest ``` Inside the `tests/multitest` directory we should also create an empty file named `mod.rs`: -```shell copy filename="TERMINAL" +```shell touch tests/multitest/mod.rs ``` And populate it with the following content (just copy and paste it): -```rust copy filename="tests/multitest/mod.rs" +```rust title="tests/multitest/mod.rs" mod test_counter; ``` Finally, inside the `tests/multitest` directory, we create a file named `test_counter.rs`: -```shell copy filename="TERMINAL" +```shell touch tests/multitest/test_counter.rs ``` @@ -121,37 +121,37 @@ Now that the directory structure for tests is ready, it's time to run all tests. Once the directories and files are set up for tests, let's execute them: -```shell copy filename="TERMINAL" +```shell cargo test ``` The expected output should be similar to the one shown below: -```ansi {6,12,16} showLineNumbers filename="OUTPUT" - Finished `test` profile [unoptimized + debuginfo] target(s) in 17.96s - Running unittests src/lib.rs (target/debug/deps/counter-f350df45a1cd1c74) +```text title="output" showLineNumbers {6,12,16} + Finished `test` profile [unoptimized + debuginfo] target(s) in 17.96s + Running unittests src/lib.rs (target/debug/deps/counter-f350df45a1cd1c74) running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - Running tests/mod.rs (target/debug/deps/mod-54761c1d31e6d0fe) + Running tests/mod.rs (target/debug/deps/mod-54761c1d31e6d0fe) running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - Doc-tests counter + Doc-tests counter running 0 tests -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Rust testing framework searches for several types of tests in the project, counts all test cases and executes them. In our example - while we haven't written any single test yet - there is nothing to -execute. The reported number of executed tests is **0** for unit tests (line 2), **0** for -integration tests (line 12) and **0** for documentation tests (line 16). +execute. The reported number of executed tests is **0** for unit tests (**line 6**), **0** for +integration tests (**line 12**) and **0** for documentation tests (**line 16**). Similarly, to execute all tests using [cargo-nextest](https://nexte.st), type: @@ -162,10 +162,10 @@ cargo nextest run The expected output is: ```ansi {4} showLineNumbers filename="OUTPUT" - Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06s - Starting 0 tests across 2 binaries (run ID: 3e0cbabb-3ef9-4b2f-98a8-d375bc510845, nextest profile: default) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06s + Starting 0 tests across 2 binaries (run ID: 3e0cbabb-3ef9-4b2f-98a8-d375bc510845, nextest profile: default) ------------ - Summary [ 0.000s] 0 tests run: 0 passed, 0 skipped + Summary [ 0.000s] 0 tests run: 0 passed, 0 skipped ``` [cargo-nextest](https://nexte.st) reports in a user-friendly manner that there were no tests to run (**line 4**). @@ -180,13 +180,13 @@ quite tedious, so let's prepare a short script to automate this task. Create an empty file named `coverage.sh` in the `counter` directory: -```shell copy filename="TERMINAL" +```shell touch coverage.sh ``` Populate it with the following content: -```shell copy filename="coverage.sh" +```bash title="coverage.sh" #!/usr/bin/env bash # generate coverage report @@ -198,43 +198,42 @@ echo "Report: file://$(pwd)/target/coverage-report/tarpaulin-report.html" Finally, make this file executable: -```shell filename="TERMINAL" +```shell chmod +x coverage.sh ``` The complete file structure of the **counter** smart contract project, with the code coverage script should now look like this: -```shell filename="TERMINAL" -tree -``` - -```ansi {3} filename="counter (directory)" -. -├── Cargo.toml -├── coverage.sh -├── src -│   ├── contract.rs -│   ├── lib.rs -│   └── msg.rs -└── tests -    ├── mod.rs -    └── multitest -        ├── mod.rs -        └── test_counter.rs +```text title="counter directory" +. +├── Cargo.toml +//highlight-next-line +├── coverage.sh +├── src +│ ├── contract.rs +│ ├── lib.rs +│ └── msg.rs +//highlight-next-line +└── tests + ├── mod.rs +//highlight-next-line + └── multitest + ├── mod.rs + └── test_counter.rs ``` ## Measuring code coverage With the code coverage script at hand, measuring code coverage is now as simple as typing: -```shell copy filename="TERMINAL" +```shell ./coverage.sh ``` The result should be similar to this (only the last few lines are shown): -```ansi filename="OUTPUT" +```text title="output" ⋮ || Tested/Total Lines: || src/contract.rs: 0/18 @@ -247,7 +246,7 @@ Additionally, Tarpaulin generates a coverage report in HTML format, that can be As expected, the current code coverage for the **counter** smart contract is **0.00%** since we haven't written any tests yet. Follow the next chapters, and make the code coverage report shine green. -```rust title="Code coverage report" showLineNumbers {11-25,28-42,45-51} +```text title="Code coverage report" showLineNumbers #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -258,6 +257,7 @@ use cw_storage_plus::Item; const COUNTER: Item = Item::new("value"); #[cfg_attr(not(feature = "library"), entry_point)] +//no-coverage-start pub fn instantiate( deps: DepsMut, _env: Env, @@ -273,8 +273,10 @@ pub fn instantiate( )?; Ok(Response::default()) } +//no-coverage-end #[cfg_attr(not(feature = "library"), entry_point)] +//no-coverage-start pub fn execute( deps: DepsMut, _env: Env, @@ -290,8 +292,10 @@ pub fn execute( })?; Ok(Response::default()) } +//no-coverage-end #[cfg_attr(not(feature = "library"), entry_point)] +//no-coverage-start pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result { match msg { CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse { @@ -299,6 +303,7 @@ pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result