diff --git a/src/pages/tutorial/cw-contract/_meta.json b/src/pages/tutorial/cw-contract/_meta.json
index d84ab30f..adff6584 100644
--- a/src/pages/tutorial/cw-contract/_meta.json
+++ b/src/pages/tutorial/cw-contract/_meta.json
@@ -4,5 +4,6 @@
"building-contract": "Building the contract",
"query": "Creating a query",
"testing-query": "Testing the query",
- "multitest-introduction": "Multitest introduction"
+ "multitest-introduction": "Multitest introduction",
+ "state": "Storing state"
}
diff --git a/src/pages/tutorial/cw-contract/state.mdx b/src/pages/tutorial/cw-contract/state.mdx
new file mode 100644
index 00000000..c79ebd9c
--- /dev/null
+++ b/src/pages/tutorial/cw-contract/state.mdx
@@ -0,0 +1,658 @@
+import { Tabs } from "nextra/components";
+
+# Contract state
+
+The contract we are working on already has some behavior that can answer a query. Unfortunately, it
+is very predictable with its answers, and it has no way of altering them. In this chapter, I
+introduce the notion of state, which allows us to bring true life to a smart contract.
+
+We'll keep the state static for now - it will be initialized on contract instantiation. The state
+will contain a list of admins who will be eligible to execute messages in the future.
+
+The first thing to do is to update `Cargo.toml` with yet another dependency. We have two options:
+
+- [`cw-storage-plus`](../../cw-storage-plus) - crate established in the CosmWasm ecosystem,
+- [`storey`](../../storey) - crate presenting new approach to state management.
+
+Both of them provide high-level bindings for CosmWasm smart contracts state management.
+
+
+
+
+```toml {12} filename="Cargo.toml"
+[package]
+name = "contract"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+cosmwasm-std = { version = "2.1.4", features = ["staking"] }
+serde = { version = "1.0.214", default-features = false, features = ["derive"] }
+cw-storey = "0.4.0"
+
+[dev-dependencies]
+cw-multi-test = "2.2.0"
+```
+
+
+
+
+```toml {12} filename="Cargo.toml"
+[package]
+name = "contract"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+cosmwasm-std = { version = "2.1.4", features = ["staking"] }
+serde = { version = "1.0.214", default-features = false, features = ["derive"] }
+cw-storage-plus = "2.0.0"
+
+[dev-dependencies]
+cw-multi-test = "2.2.0"
+```
+
+
+
+
+Now create a new file where you will keep the state for the contract - we typically call it
+`src/state.rs`:
+
+
+
+
+```rust {4} filename="src/state.rs"
+use cosmwasm_std::Addr;
+use cw_storey::containers::Item;
+
+const ADMIN_ID: u8 = 0;
+pub const ADMINS: Item> = Item::new(ADMIN_ID);
+```
+
+
+
+
+```rust {4} filename="src/state.rs"
+use cosmwasm_std::Addr;
+use cw_storage_plus::Item;
+
+pub const ADMINS: Item> = Item::new("admins");
+```
+
+
+
+
+And make sure to declare the module in `src/lib.rs`:
+
+```rust {8} filename="src/lib.rs"
+use cosmwasm_std::{
+ entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult,
+};
+use msg::QueryMsg;
+
+mod contract;
+mod msg;
+mod state;
+
+#[entry_point]
+pub fn instantiate(deps: DepsMut, env: Env, info: MessageInfo, msg: Empty) -> StdResult {
+ contract::instantiate(deps, env, info, msg)
+}
+
+#[entry_point]
+pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult {
+ contract::query(deps, env, msg)
+}
+```
+
+The new thing we have here is the `ADMINS` constant of type `Item>`. You could ask an
+excellent question here - how is the state constant? How do I modify it if it is a constant value?
+
+The answer is tricky - this constant is not keeping the state itself. The state is stored in the
+blockchain and is accessed via the `deps` argument passed to entry points. The
+[storage-plus](../../cw-storage-plus) and [storey](../../storey) constants are just accessor
+utilities helping us access this state in a structured way.
+
+In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with
+metainformation pointing to the contract which owns them (so no other contract can alter them in any
+way), but even after removing the prefixes, the single contract state is a smaller key-value pair.
+
+Both crates handle more complex state structures by additionally prefixing items keys intelligently.
+For now, we just used the simplest storage entity - an
+[`cw_storage_plus::Item<_>`](../../cw-storage-plus/containers/item) and an
+[`storey::Item<_>`](../../storey/containers/item), which holds a single optional value of a given
+type - `Vec` in this case. And what would be a key to this item in the storage? It doesn't
+matter to us - it would be figured out to be unique, based on a unique string passed to the `new`
+function.
+
+Before we work on initializing our state, we need some better instantiate message. Go to
+`src/msg.rs` and create one:
+
+```rust {3-6} filename="src/msg.rs"
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct InstantiateMsg {
+ pub admins: Vec,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct GreetResp {
+ pub message: String,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub enum QueryMsg {
+ Greet {},
+}
+```
+
+Now go forward to instantiate the entry point in `src/contract.rs`, and initialize our state to
+whatever we got in the instantiation message:
+
+
+
+
+```rust {1-2, 14-21} filename="src/contract.rs"
+use crate::msg::{GreetResp, InstantiateMsg, QueryMsg};
+use crate::state::ADMINS;
+use cosmwasm_std::{
+ to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult,
+};
+use cw_storey::CwStorage;
+
+pub fn instantiate(
+ deps: DepsMut,
+ _env: Env,
+ _info: MessageInfo,
+ msg: InstantiateMsg,
+) -> StdResult {
+ let admins = msg
+ .admins
+ .into_iter()
+ .map(|addr| deps.api.addr_validate(&addr))
+ .collect::>>()?;
+
+ let mut cw_storage = CwStorage(deps.storage);
+ ADMINS.access(&mut cw_storage).set(&admins)?;
+
+ Ok(Response::new())
+}
+
+pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
+ use QueryMsg::*;
+
+ match msg {
+ Greet {} => to_json_binary(&query::greet()?),
+ }
+}
+
+pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult {
+ unimplemented!()
+}
+
+mod query {
+ use super::*;
+
+ pub fn greet() -> StdResult {
+ let resp = GreetResp {
+ message: "Hello World".to_owned(),
+ };
+
+ Ok(resp)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use cw_multi_test::{App, ContractWrapper, Executor, IntoAddr};
+
+ use super::*;
+
+ #[test]
+ fn greet_query() {
+ let mut app = App::default();
+
+ let code = ContractWrapper::new(execute, instantiate, query);
+ let code_id = app.store_code(Box::new(code));
+ let owner = "owner".into_addr();
+ let admin = "admin".into_addr();
+
+ let addr = app
+ .instantiate_contract(
+ code_id,
+ owner,
+ &Empty {},
+ &[],
+ "Contract",
+ None,
+ )
+ .unwrap();
+
+ let resp: GreetResp = app
+ .wrap()
+ .query_wasm_smart(addr, &QueryMsg::Greet {})
+ .unwrap();
+
+ assert_eq!(
+ resp,
+ GreetResp {
+ message: "Hello World".to_owned()
+ }
+ );
+ }
+}
+```
+
+
+
+
+```rust {1-2, 13-18} filename="src/contract.rs"
+use crate::msg::{GreetResp, InstantiateMsg, QueryMsg};
+use crate::state::ADMINS;
+use cosmwasm_std::{
+ to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult,
+};
+
+pub fn instantiate(
+ deps: DepsMut,
+ _env: Env,
+ _info: MessageInfo,
+ msg: InstantiateMsg,
+) -> StdResult {
+ let admins: StdResult> = msg
+ .admins
+ .into_iter()
+ .map(|addr| deps.api.addr_validate(&addr))
+ .collect();
+ ADMINS.save(deps.storage, &admins?)?;
+
+ Ok(Response::new())
+}
+
+pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
+ use QueryMsg::*;
+
+ match msg {
+ Greet {} => to_json_binary(&query::greet()?),
+ }
+}
+
+pub fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult {
+ unimplemented!()
+}
+
+mod query {
+ use super::*;
+
+ pub fn greet() -> StdResult {
+ let resp = GreetResp {
+ message: "Hello World".to_owned(),
+ };
+
+ Ok(resp)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use cw_multi_test::{App, ContractWrapper, Executor};
+
+ use super::*;
+
+ #[test]
+ fn greet_query() {
+ let mut app = App::default();
+
+ let code = ContractWrapper::new(execute, instantiate, query);
+ let code_id = app.store_code(Box::new(code));
+ let owner = "owner".into_addr();
+ let admin = "admin".into_addr();
+
+ let addr = app
+ .instantiate_contract(
+ code_id,
+ owner,
+ &Empty {},
+ &[],
+ "Contract",
+ None,
+ )
+ .unwrap();
+
+ let resp: GreetResp = app
+ .wrap()
+ .query_wasm_smart(addr, &QueryMsg::Greet {})
+ .unwrap();
+
+ assert_eq!(
+ resp,
+ GreetResp {
+ message: "Hello World".to_owned()
+ }
+ );
+ }
+}
+```
+
+
+
+
+We also need to update the message type on entry point in `src/lib.rs`:
+
+```rust {13} filename="src/lib.rs"
+use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
+use msg::{InstantiateMsg, QueryMsg};
+
+mod contract;
+mod msg;
+mod state;
+
+#[entry_point]
+pub fn instantiate(
+ deps: DepsMut,
+ env: Env,
+ info: MessageInfo,
+ msg: InstantiateMsg,
+) -> StdResult {
+ contract::instantiate(deps, env, info, msg)
+}
+
+#[entry_point]
+pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult {
+ contract::query(deps, env, msg)
+}
+```
+
+Voila, that's all that is needed to update the state!
+
+First, we need to transform the vector of strings into the vector of addresses to be stored. We
+cannot take addresses as a message argument because not every string is a valid address. It might be
+a bit confusing when compared to working on tests. In tests, any string could be used as an address.
+Let me explain.
+
+Every string can be technically considered an address. However, not every string is an actual
+existing blockchain address. When we keep anything of type `Addr` in the contract, we assume it is a
+proper address in the blockchain. That is why the
+[`addr_validate`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Api.html#tymethod.addr_validate)
+function exits - to check this precondition.
+
+Having data to store, we use the
+
+[`save`](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html#method.save) method
+in case of [cw-storage-plus](../../cw-storage-plus/), or
+[`set`](https://docs.rs/storey/latest/storey/containers/struct.Item.html#method.set) method in case
+of [storey](../../storey), to write it into the contract state. Note that the first argument for
+these methods is
+[`&mut Storage`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/trait.Storage.html), which is
+actual blockchain storage. As emphasized, the `Item` object stores nothing and is just an accessor.
+It determines how to store the data in the storage given to it. The second argument is the
+serializable data to be stored.
+
+It is a good time to check if the regression we have passes - try running our tests:
+
+```shell copy filename="TERMINAL"
+cargo test
+```
+
+```shell filename="TERMINAL"
+running 1 test
+test contract::tests::greet_query ... FAILED
+
+failures:
+
+---- contract::tests::greet_query stdout ----
+thread 'contract::tests::greet_query' panicked at src/contract.rs:67:14:
+called `Result::unwrap()` on an `Err` value: Error executing WasmMsg:
+ sender: cosmwasm1fsgzj6t7udv8zhf6zj32mkqhcjcpv52yph5qsdcl0qt94jgdckqs2g053y
+ Instantiate { admin: None, code_id: 1, msg: {}, funds: [], label: "Contract" }
+
+Caused by:
+ Error parsing into type contract::msg::InstantiateMsg: missing field `admins`
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+
+
+failures:
+ contract::tests::greet_query
+
+test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
+
+error: test failed, to rerun pass `--lib`
+```
+
+Damn, we broke something! But be calm. Let's start with carefully reading an error message:
+
+```shell filename="TERMINAL"
+> Error parsing into type contract::msg::InstantiateMsg: missing field `admins`',
+> src/contract.rs:67:14
+```
+
+The problem is that in the test, we send an empty instantiation message, but right now, our endpoint
+expects to have an `admin` field. The MultiTest framework tests contract from the entry point to
+results, so sending messages using MT functions first serializes them. Then the contract
+deserializes them on the entry. But now it tries to deserialize the empty JSON to some non-empty
+message! We can quickly fix it by updating the test. To shorten the code snippet we will present
+only the test part:
+
+```rust {14, 20-22} filename="src/contract.rs"
+#[cfg(test)]
+mod tests {
+ use cw_multi_test::{App, ContractWrapper, Executor, IntoAddr};
+
+ use super::*;
+
+ #[test]
+ fn greet_query() {
+ let mut app = App::default();
+
+ let code = ContractWrapper::new(execute, instantiate, query);
+ let code_id = app.store_code(Box::new(code));
+ let owner = "owner".into_addr();
+ let admin = "admin".into_addr();
+
+ let addr = app
+ .instantiate_contract(
+ code_id,
+ owner,
+ &InstantiateMsg {
+ admins: vec![admin.to_string()],
+ },
+ &[],
+ "Contract",
+ None,
+ )
+ .unwrap();
+
+ let resp: GreetResp = app
+ .wrap()
+ .query_wasm_smart(addr, &QueryMsg::Greet {})
+ .unwrap();
+
+ assert_eq!(
+ resp,
+ GreetResp {
+ message: "Hello World".to_owned()
+ }
+ );
+ }
+}
+```
+
+## Testing state
+
+When the state is initialized, we want a way to test it. We want to provide a query to check if the
+instantiation affects the state. Just create a simple one listing all admins. Start with adding a
+variant for query message and a corresponding response message in `src/msg.rs`. We'll add the
+variant `AdminsList`, the response `AdminsListResp`, and have it return a vector of
+[`Addr`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Addr.html)s:
+
+```rust {1, 14-23} filename="src/msg.rs"
+use cosmwasm_std::Addr;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct InstantiateMsg {
+ pub admins: Vec,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct GreetResp {
+ pub message: String,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub struct AdminsListResp {
+ pub admins: Vec,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+pub enum QueryMsg {
+ Greet {},
+ AdminsList {},
+}
+```
+
+And implement it in `src/contract.rs`. Again we will show only the part of the code that changed.
+
+
+
+
+```rust filename="src/contract.rs"
+use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg};
+
+// ...
+
+pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
+ use QueryMsg::*;
+
+ match msg {
+ Greet {} => to_json_binary(&query::greet()?),
+ AdminsList {} => to_json_binary(&query::admins_list(deps)?),
+ }
+}
+
+// ...
+
+pub fn admins_list(deps: Deps) -> StdResult {
+ let cw_storage = CwStorage(deps.storage);
+ let admins = ADMINS.access(&cw_storage).get()?;
+ let resp = AdminsListResp {
+ admins: admins.unwrap_or_default(),
+ };
+ Ok(resp)
+}
+```
+
+
+
+
+```rust filename="src/contract.rs"
+use crate::msg::{AdminsListResp, GreetResp, InstantiateMsg, QueryMsg};
+
+// ...
+
+pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
+ use QueryMsg::*;
+
+ match msg {
+ Greet {} => to_json_binary(&query::greet()?),
+ AdminsList {} => to_json_binary(&query::admins_list(deps)?),
+ }
+}
+
+// ...
+
+pub fn admins_list(deps: Deps) -> StdResult {
+ let admins = ADMINS.load(deps.storage)?;
+ let resp = AdminsListResp { admins };
+ Ok(resp)
+}
+```
+
+
+
+
+Now when we have the tools to test the instantiation, let's write a test case:
+
+```rust filename="src/contract.rs"
+// ...
+
+#[cfg(test)]
+mod tests {
+ use cw_multi_test::{App, ContractWrapper, Executor, IntoAddr};
+
+ use crate::msg::AdminsListResp;
+
+ use super::*;
+
+ #[test]
+ fn instantiation() {
+ let mut app = App::default();
+
+ let code = ContractWrapper::new(execute, instantiate, query);
+ let code_id = app.store_code(Box::new(code));
+ let owner = "owner".into_addr();
+ let admin1 = "admin1".into_addr();
+ let admin2 = "admin2".into_addr();
+
+ let addr = app
+ .instantiate_contract(
+ code_id,
+ owner.clone(),
+ &InstantiateMsg { admins: vec![] },
+ &[],
+ "Contract",
+ None,
+ )
+ .unwrap();
+
+ let resp: AdminsListResp = app
+ .wrap()
+ .query_wasm_smart(addr, &QueryMsg::AdminsList {})
+ .unwrap();
+
+ assert_eq!(resp, AdminsListResp { admins: vec![] });
+
+ let addr = app
+ .instantiate_contract(
+ code_id,
+ owner,
+ &InstantiateMsg {
+ admins: vec![admin1.to_string(), admin2.to_string()],
+ },
+ &[],
+ "Contract 2",
+ None,
+ )
+ .unwrap();
+
+ let resp: AdminsListResp = app
+ .wrap()
+ .query_wasm_smart(addr, &QueryMsg::AdminsList {})
+ .unwrap();
+
+ assert_eq!(
+ resp,
+ AdminsListResp {
+ admins: vec![admin1, admin2]
+ }
+ );
+ }
+
+ // ...
+}
+```
+
+The test is simple - instantiate the contract twice with different initial admins, and ensure the
+query result is proper each time. This is often the way we test our contract - we execute bunch o
+messages on the contract, and then we query it for some data, verifying if query responses are as
+expected.
+
+We are doing a pretty good job developing our contract. Now it is time to use the state and allow
+for some executions.