PageSourceSearch

https://cosmwasm.github.io/assets/js/0c86cb21.6a7ea7a8.js

js cosmwasm.github.io collected 2026-10-03 08:50:27 UTC 19,671 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkcosmwasm_documentation=self.webpackChunkcosmwasm_documentation||[]).push([["3251"],{6953(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>u,frontMatter:()=>a,contentTitle:()=>o,toc:()=>c,assets:()=>d});var s=JSON.parse('{"id":"tutorial/writing-contracts/execution","title":"Execution messages","description":"We went through instantiate and query messages. It is finally time to introduce the last basic entrypoint:","source":"@site/docs/tutorial/writing-contracts/execution.md","sourceDirName":"tutorial/writing-contracts","slug":"/tutorial/writing-contracts/execution","permalink":"/tutorial/writing-contracts/execution","draft":false,"unlisted":false,"tags":[],"version":"current","lastUpdatedAt":1760360351000,"sidebarPosition":8,"frontMatter":{"sidebar_position":8},"sidebar":"tutorialSidebar","previous":{"title":"Storing state","permalink":"/tutorial/writing-contracts/state"},"next":{"title":"Passing events","permalink":"/tutorial/writing-contracts/event"}}'),r=t(4848),i=t(8453);let a={sidebar_position:8},o="Execution messages",d={},c=[{value:"Error handling",id:"error-handling",level:2},{value:"Custom error and MultiTest",id:"custom-error-and-multitest",level:2}];function l(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"execution-messages",children:"Execution messages"})}),"\n",(0,r.jsx)(n.p,{children:"We went through instantiate and query messages. It is finally time to introduce the last basic entrypoint:\nthe execute messages. It is similar to what we have done so far, and it should be just revisiting our knowledge."}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsx)(n.p,{children:"We encourage you to try implementing what we are describing here on your own as an exercise,\nwithout checking out the source code."})}),"\n",(0,r.jsx)(n.p,{children:"The idea of the contract will be easy - every contract admin would be eligible to call two execute messages:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"AddMembers"})," - this message would allow the admin to add another address to the admin's list,"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Leave"})," - would allow an admin to remove himself from the list."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Not too complicated. Let's start with defining messages:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/msg.rs" {9-13}',children:"use cosmwasm_std::Addr;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\npub struct InstantiateMsg {\n    pub admins: Vec<String>,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\npub enum ExecuteMsg {\n    AddMembers { admins: Vec<String> },\n    Leave {},\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\npub struct GreetResp {\n    pub message: String,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\npub struct AdminsListResp {\n    pub admins: Vec<Addr>,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\npub enum QueryMsg {\n    Greet {},\n    AdminsList {},\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"And implement execute handling:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:'use crate::msg::{ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg};\nuse crate::state::ADMINS;\nuse cosmwasm_std::{\n    to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult,\n};\n\n// ...\n\npub fn execute(\n    deps: DepsMut,\n    _env: Env,\n    info: MessageInfo,\n    msg: ExecuteMsg,\n) -> StdResult<Response> {\n    use ExecuteMsg::*;\n\n    match msg {\n        AddMembers { admins } => exec::add_members(deps, info, admins),\n        Leave {} => exec::leave(deps, info),\n    }\n}\n\nmod exec {\n    use cosmwasm_std::StdError;\n\n    use super::*;\n\n    pub fn add_members(\n        deps: DepsMut,\n        info: MessageInfo,\n        admins: Vec<String>,\n    ) -> StdResult<Response> {\n        let mut curr_admins = ADMINS.load(deps.storage)?;\n        if !curr_admins.contains(&info.sender) {\n            return Err(StdError::generic_err("Unauthorised access"));\n        }\n\n        let admins: StdResult<Vec<_>> = admins\n            .into_iter()\n            .map(|addr| deps.api.addr_validate(&addr))\n            .collect();\n\n        curr_admins.append(&mut admins?);
1\n        ADMINS.save(deps.storage, &curr_admins)?;\n\n        Ok(Response::new())\n    }\n\n    pub fn leave(deps: DepsMut, info: MessageInfo) -> StdResult<Response> {\n        ADMINS.update(deps.storage, move |admins| -> StdResult<_> {\n            let admins = admins\n                .into_iter()\n                .filter(|admin| *admin != info.sender)\n                .collect();\n            Ok(admins)\n        })?;\n\n        Ok(Response::new())\n    }\n}\n\n// ...\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The entry point itself also has to be created in ",(0,r.jsx)(n.code,{children:"src/lib.rs"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/lib.rs"',children:"use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};\nuse msg::{ExecuteMsg, InstantiateMsg, QueryMsg};\n\nmod contract;\nmod msg;\nmod state;\n\n#[entry_point]\npub fn instantiate(\n    deps: DepsMut,\n    env: Env,\n    info: MessageInfo,\n    msg: InstantiateMsg,\n) -> StdResult<Response> {\n    contract::instantiate(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {\n    contract::execute(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {\n    contract::query(deps, env, msg)\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["There are a couple of new things, but nothing significant. First is how do I reach the message\nsender to verify he is an admin or remove him from the list - I used the ",(0,r.jsx)(n.code,{children:"info.sender"})," field of\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.MessageInfo.html",children:(0,r.jsx)(n.code,{children:"MessageInfo"})}),", which is\nhow it looks like - the member. As the message is always sent from the proper address, the ",(0,r.jsx)(n.code,{children:"sender"}),"\nis already of the ",(0,r.jsx)(n.code,{children:"Addr"})," type - no need to validate it. Another new thing is the\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html#method.update",children:(0,r.jsx)(n.code,{children:"update"})}),"\nfunction on an ",(0,r.jsx)(n.code,{children:"Item"})," - it makes a read and update of an entity potentially more efficient. It is\npossible to do it by reading admins first, then updating and storing the result."]}),"\n",(0,r.jsxs)(n.p,{children:["You probably noticed that when working with ",(0,r.jsx)(n.code,{children:"Item"}),", we always assume something is there. But nothing\nforces us to initialize the ",(0,r.jsx)(n.code,{children:"ADMINS"})," value on instantiation! So what happens there? Well, both\n",(0,r.jsx)(n.code,{children:"load"})," and ",(0,r.jsx)(n.code,{children:"update"})," functions would return an error. But there is a\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html#method.may_load",children:(0,r.jsx)(n.code,{children:"may_load"})}),"\nfunction, which returns ",(0,r.jsx)(n.code,{children:"StdResult<Option<T>>"})," - it would return ",(0,r.jsx)(n.code,{children:"Ok(None)"})," in case of empty\nstorage. There is even a possibility to remove an existing item from storage with the\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html#method.remove",children:(0,r.jsx)(n.code,{children:"remove"})}),"\nfunction."]}),"\n",(0,r.jsx)(n.p,{children:"One thing to improve is error handling. While validating the sender to be admin, we are returning\nsome arbitrary string as an error. We can do better."}),"\n",(0,r.jsx)(n.h2,{id:"error-handling",children:"Error handling"}),"\n",(0,r.jsxs)(n.p,{children:["In our contract, we now have an error situation when a user tries to execute ",(0,r.jsx)(n.code,{children:"AddMembers"})," not being\nan admin himself. There is no proper error case in\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.StdError.html",children:(0,r.jsx)(n.code,{children:"StdError"})})," to report this\nsituation, so we have to return a generic error with a message. It is not the best approach."]}),"\n",(0,r.jsxs)(n.p,{children:["For error reporting, we encourage using\n",(0,r.jsx)(n.a,{href:"https://crates.io/crates/thiserror/2.0.3/dependencies",children:(0,r.jsx)(n.code,{children:"thiserror"})})," crate. Start with updating your\ndependencies:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-toml",metastring:'title="Cargo.toml" {13}',children:'[package]\nname = "contract"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\ncrate-type = ["cdylib"]\n\n[dependencies]\ncosmwasm-std = { version = "2.1.4", features = ["staking"] }\nserde = { version = "1.0.214", default-features = false, features = ["derive"] }\ncw-storage-plus = "2.0.0"\nthiserror = "2.0.3"\n\n[dev-dependencies]\ncw-multi-test = "2.2.0"\n'})}),"\n",(0,r.jsx)(n.p,{children:"Now we define an error."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/error.rs"',children:'use cosmwasm_std::{Addr, StdError};\nuse thiserror::Error;\n\n#[derive(Error, Debug, PartialEq)]\npub enum ContractError {\n    #[error("{0}")]\n    StdError(#[from] StdError),\n    #[error("{sender} is not contract admin")]\n    Unauthorized { sender: Addr },\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["We also need to add the new module to ",(0,r.jsx)(n.code,{children:"src/lib.rs"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/lib.rs" {5}',children:"use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};\nuse msg::{ExecuteMsg, InstantiateMsg, QueryMsg};\n\nmod contract;\nmod error;\nmod msg;\nmod state;\n\n#[entry_point]\npub fn instantiate(\n    deps: DepsMut,\n    env: Env,\n    info: MessageInfo,\n    msg: InstantiateMsg,\n) -> StdResult<Response> {\n    contract::instantiate(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {\n    contract::execute(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {\n    contract::query(deps, env, msg)\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Using ",(0,r.jsx)(n.a,{href:"https://crates.io/crates/thiserror",children:(0,r.jsx)(n.code,{children:"thiserror"})})," we define errors like a simple enum, and the\ncrate ensures that the type implements\n",(0,r.jsx)(n.a,{href:"https://doc.rust-lang.org/std/error/trait.Error.html",children:(0,r.jsx)(n.code,{children:"std::error::Error"})})," trait. A very nice\nfeature of this crate is the inline definition of\n",(0,r.jsx)(n.a,{href:"https://doc.rust-lang.org/std/fmt/trait.Display.html",children:(0,r.jsx)(n.code,{children:"Display"})})," trait by an ",(0,r.jsx)(n.code,{children:"#[error]"})," attribute.\nAlso, another helpful thing is the ",(0,r.jsx)(n.code,{children:"#[from]"})," attribute, which automatically generates proper\n",(0,r.jsx)(n.a,{href:"https://doc.rust-lang.org/std/convert/trait.From.html",children:(0,r.jsx)(n.code,{children:"From"})})," implementation, so it is easy to use\n",(0,r.jsx)(n.code,{children:"?"})," operator with ",(0,r.jsx)(n.code,{children:"thiserror"})," types."]}),"\n",(0,r.jsx)(n.p,{children:"Now update the execute endpoint to use our new error type."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs" {13,29,32-34,48}',children:"use crate::error::ContractError;\nuse crate::msg::{AdminsListResp, ExecuteMsg, GreetResp, InstantiateMsg, QueryMsg};\nuse crate::state::ADMINS;\nuse cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};\n\n// ...\n\npub fn execute(\n    deps: DepsMut,\n    _env: Env,\n    info: MessageInfo,\n    msg: ExecuteMsg,\n) -> Result<Response, ContractError> {\n    use ExecuteMsg::*;\n\n    match msg {\n        AddMembers { admins } => exec::add_members(deps, info, admins),\n        Leave {} => exec::leave(deps, info),\n    }\n}\n\nmod exec {\n    use super::*;\n\n    pub fn add_members(\n        deps: DepsMut,\n        info: MessageInfo,\n        admins: Vec<String>,\n    ) -> Result<Response, ContractError> {\n        let mut curr_admins = ADMINS.load(deps.storage)?;\n        if !curr_admins.contains(&info.sender) {\n            return Err(ContractError::Unauthorized {\n                sender: info.sender,\n            });\n        }\n\n        let admins: StdResult<Vec<_>> = admins\n            .into_iter()\n            .map(|addr| deps.api.addr_validate(&addr))\n            .collect();\n\n        curr_admins.append(&mut admins?);
1\n        ADMINS.save(deps.storage, &curr_admins)?;\n\n        Ok(Response::new())\n    }\n\n    pub fn leave(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {\n        ADMINS.update(deps.storage, move |admins| -> StdResult<_> {\n            let admins = admins\n                .into_iter()\n                .filter(|admin| *admin != info.sender)\n                .collect();\n            Ok(admins)\n        })?;\n\n        Ok(Response::new())\n    }\n}\n\n// ...\n"})}),"\n",(0,r.jsx)(n.p,{children:"The entry point return type also has to be updated:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/lib.rs" {2,26}',children:"use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};\nuse error::ContractError;\nuse msg::{ExecuteMsg, InstantiateMsg, QueryMsg};\n\nmod contract;\nmod error;\nmod msg;\nmod state;\n\n#[entry_point]\npub fn instantiate(\n    deps: DepsMut,\n    env: Env,\n    info: MessageInfo,\n    msg: InstantiateMsg,\n) -> StdResult<Response> {\n    contract::instantiate(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn execute(\n    deps: DepsMut,\n    env: Env,\n    info: MessageInfo,\n    msg: ExecuteMsg,\n) -> Result<Response, ContractError> {\n    contract::execute(deps, env, info, msg)\n}\n\n#[entry_point]\npub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {\n    contract::query(deps, env, msg)\n}\n"})}),"\n",(0,r.jsx)(n.h2,{id:"custom-error-and-multitest",children:"Custom error and MultiTest"}),"\n",(0,r.jsxs)(n.p,{children:["Using proper custom error type has one nice upside - MultiTest is maintaining error type using the\n",(0,r.jsx)(n.a,{href:"https://crates.io/crates/anyhow",children:(0,r.jsx)(n.code,{children:"anyhow"})})," crate. It is a sibling of ",(0,r.jsx)(n.code,{children:"thiserror"}),", designed to\nimplement type-erased errors in a way that allows getting the original error back."]}),"\n",(0,r.jsx)(n.p,{children:"Let's write a test that verifies that a non-admin cannot add himself to a list:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:'// ...\n\n#[cfg(test)]\nmod tests {\n    use cosmwasm_std::Addr;\n    use cw_multi_test::{App, ContractWrapper, Executor, IntoAddr};\n\n    use crate::msg::AdminsListResp;\n\n    use super::*;\n\n    // ...\n\n    #[test]\n    fn unauthorized() {\n        let mut app = App::default();\n\n        let code = ContractWrapper::new(execute, instantiate, query);\n        let code_id = app.store_code(Box::new(code));\n        let owner = "owner".into_addr();\n\n        let addr = app\n            .instantiate_contract(\n                code_id,\n                owner.clone(),\n                &InstantiateMsg { admins: vec![] },\n                &[],\n                "Contract",\n                None,\n            )\n            .unwrap();\n\n        let err = app\n            .execute_contract(\n                owner.clone(),\n                addr,\n                &ExecuteMsg::AddMembers {\n                    admins: vec!["user".to_owned()],\n                },\n                &[],\n            )\n            .unwrap_err();\n\n        assert_eq!(\n            ContractError::Unauthorized { sender: owner },\n            err.downcast().unwrap()\n        );\n    }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Executing a contract is very similar to any other call - we use an\n",(0,r.jsx)(n.a,{href:"https://docs.rs/cw-multi-test/latest/cw_multi_test/trait.Executor.html#method.execute_contract",children:(0,r.jsx)(n.code,{children:"execute_contract"})}),"\nfunction. As the execution may fail, we get an error type out of this call, but instead of calling\n",(0,r.jsx)(n.code,{children:"unwrap"})," to extract a value out of it, we expect an error to occur - this is the purpose of the\n",(0,r.jsx)(n.a,{href:"https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err",children:(0,r.jsx)(n.code,{children:"unwrap_err"})})," call. Now,\nas we have an error value, we can check if it matches what we expected with an ",(0,r.jsx)(n.code,{children:"assert_eq!"}),". There\nis a slight complication - the error returned from ",(0,r.jsx)(n.code,{children:"execute_contract"})," is an\n",(0,r.jsx)(n.a,{href:"https://docs.rs/anyhow/latest/anyhow/struct.Error.html",children:(0,r.jsx)(n.code,{children:"anyhow::Error"})})," error, but we expect it to\nbe a ",(0,r.jsx)(n.code,{children:"ContractError"}),". Fortunately, as I said before, ",(0,r.jsx)(n.code,{children:"anyhow"})," errors can recover their original type\nusing the ",(0,r.jsx)(n.a,{href:"https://docs.rs/anyhow/latest/anyhow/struct.Error.html#method.downcast",children:(0,r.jsx)(n.code,{children:"downcast"})}),"\nfunction. The ",(0,r.jsx)(n.code,{children:"unwrap"})," right after it is needed because downcasting may fail. The reason is that\n",(0,r.jsx)(n.code,{children:"downcast"})," doesn't magically know the type kept in the underlying error. It deduces it by some\ncontext - here, it knows we expect it to be a ",(0,r.jsx)(n.code,{children:"ContractError"}),", because of being compared to it -\ntype elision miracles. But if the underlying error would not be a ",(0,r.jsx)(n.code,{children:"ContractError"}),", then ",(0,r.jsx)(n.code,{children:"unwrap"}),"\nwould panic."]}),"\n",(0,r.jsx)(n.p,{children:"We just created a simple failure test for execution, but it is not enough to claim the contract is\nproduction-ready. All reasonable ok-cases should be covered for that. I encourage you to create some\ntests and experiment with them as an exercise after this chapter."})]})}function u(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>o});var s=t(6540);let r={},i=s.createContext(r);function a(e){let n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.