1"use strict";(self.webpackChunkcosmwasm_documentation=self.webpackChunkcosmwasm_documentation||[]).push([["9519"],{7280(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>m,frontMatter:()=>c,contentTitle:()=>l,toc:()=>u,assets:()=>d});var s=JSON.parse('{"id":"tutorial/platform/first-contract-register","title":"First execute message","description":"Add a function to your smart contract so you can register a name to an address via a transaction.","source":"@site/docs/tutorial/platform/06-first-contract-register.md","sourceDirName":"tutorial/platform","slug":"/tutorial/platform/first-contract-register","permalink":"/tutorial/platform/first-contract-register","draft":false,"unlisted":false,"tags":[],"version":"current","lastUpdatedAt":1763730226000,"sidebarPosition":6,"frontMatter":{"title":"First execute message","description":"Add a function to your smart contract so you can register a name to an address via a transaction."},"sidebar":"tutorialSidebar","previous":{"title":"First contract","permalink":"/tutorial/platform/first-contract"},"next":{"title":"First contract query","permalink":"/tutorial/platform/first-contract-query"}}'),r=n(4848),a=n(8453),i=n(773),o=n(7250);let c={title:"First execute message",description:"Add a function to your smart contract so you can register a name to an address via a transaction."},l="First execute message",d={},u=[{value:"The execute message",id:"the-execute-message",level:2},{value:"The storage definition",id:"the-storage-definition",level:2},{value:"A new error",id:"a-new-error",level:2},{value:"The execute function",id:"the-execute-function",level:2},{value:"Unit testing",id:"unit-testing",level:2},{value:"Conclusion",id:"conclusion",level:2}];function h(e){let t={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.header,{children:(0,r.jsx)(t.h1,{id:"first-execute-message",children:"First execute message"})}),"\n",(0,r.jsx)(t.p,{children:"In the previous section you created a barebones smart contract. It exists but does not do much.\nIn this section, you are going to have it do something: your smart contract is going to handle a transaction message."}),"\n",(0,r.jsxs)(t.p,{children:[":::note ",(0,r.jsx)(t.code,{children:"Exercise progression"})]}),"\n",(0,r.jsxs)(t.p,{children:["If you skipped the previous section, you can just switch the project to its ",(0,r.jsx)(t.a,{href:"https://github.com/b9lab/cw-my-nameservice/tree/first-unit-test",children:(0,r.jsx)(t.code,{children:"first-unit-test"})})," branch and take it from there."]}),"\n",(0,r.jsx)(t.p,{children:":::"}),"\n",(0,r.jsx)(t.h2,{id:"the-execute-message",children:"The execute message"}),"\n",(0,r.jsxs)(t.p,{children:["First off, you have to decide what message it is going to execute. You are implementing a nameservice,\nso it is reasonable to pick a message that will ",(0,r.jsx)(t.strong,{children:"register a name"}),"."]}),"\n",(0,r.jsxs)(t.p,{children:["Add the following code in ",(0,r.jsx)(t.code,{children:"src/msg.rs"}),":"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/msg.rs"',children:" use cosmwasm_schema::cw_serde;\n\n #[cw_serde]\n pub struct InstantiateMsg {}\n\n//diff-add-start\n+ #[cw_serde]\n+ pub enum ExecuteMsg {\n+ Register { name: String },\n+ }\n//diff-add-end\n"})}),"\n",(0,r.jsx)(t.p,{children:"Note that:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["Your transaction message, and all future variants, will fall under ",(0,r.jsx)(t.code,{children:"enum ExecuteMsg"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:["It is also serialized using ",(0,r.jsx)(t.code,{children:"cw_serde"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:["Your first variant is ",(0,r.jsx)(t.code,{children:"Register"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:["This variant only carries a ",(0,r.jsx)(t.code,{children:"name: String"}),". Implicit here is that the sender's address is the other important parameter."]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"the-storage-definition",children:"The storage definition"}),"\n",(0,r.jsxs)(t.p,{children:["Because your smart contract is meant to store the incoming information for future reference,\nyou are about to keep in storage what the names map to. To help you define storage elements,\nyou ought to use a library created to that effect. There are currently two of them,\n",(0,r.jsx)(t.a,{href:"https://docs.cosmwasm.com/cw-storage-plus",children:"StoragePlus"})," and ",(0,r.jsx)(t.a,{href:"https://docs.cosmwasm.com/storey",children:"Storey"}
1),".\nHere you add a new dependency for the CosmWasm StoragePlus elements."]}),"\n",(0,r.jsxs)(i.A,{groupId:"local-docker",children:[(0,r.jsx)(o.A,{value:"Local",default:!0,children:(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-shell",children:"cargo add cw-storage-plus2.0.0\n"})})}),(0,r.jsx)(o.A,{value:"Docker",children:(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-shell",children:"docker run --rm -it \\\n -v $(pwd):/root/ -w /root \\\n rust:1.80.1 \\\n cargo add [email protected]\n"})})})]}),"\n",(0,r.jsxs)(t.p,{children:["Then you need to describe the layout of the storage. Create the file ",(0,r.jsx)(t.code,{children:"src/state.rs"})," with:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/state.rs"',children:'use cosmwasm_schema::cw_serde;\nuse cosmwasm_std::Addr;\nuse cw_storage_plus::Map;\n\n#[cw_serde]\npub struct NameRecord {\n pub owner: Addr,\n}\n\npub const NAME_RESOLVER: Map<&[u8], NameRecord> = Map::new("name_resolver");\n'})}),"\n",(0,r.jsx)(t.p,{children:"Note how:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["You declare a new ",(0,r.jsx)(t.a,{href:"https://docs.cosmwasm.com/cw-storage-plus/containers/map",children:(0,r.jsx)(t.code,{children:"Map"})}),", a type defined in ",(0,r.jsx)(t.code,{children:"cw_storage_plus"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:["This map uses the ",(0,r.jsx)(t.a,{href:"https://docs.cosmwasm.com/cw-storage-plus/basics#keys-and-prefixes",children:"namespace"})," ",(0,r.jsx)(t.code,{children:'"name_resolver"'}),",\nwhich will be used as a ",(0,r.jsx)(t.a,{href:"https://github.com/CosmWasm/cw-storage-plus/blob/v2.0.0/src/map.rs#L64",children:"prefix"})," for all the item keys in it."]}),"\n",(0,r.jsxs)(t.li,{children:["You will use the variable named ",(0,r.jsx)(t.code,{children:"NAME_RESOLVER"})," to access items in the map."]}),"\n",(0,r.jsxs)(t.li,{children:["Keys are declared as pure bytes, ",(0,r.jsx)(t.code,{children:"[u8]"}),", for more versatility."]}),"\n",(0,r.jsxs)(t.li,{children:["A ",(0,r.jsx)(t.code,{children:"NameRecord"})," is the type of values that will be in this map."]}),"\n",(0,r.jsxs)(t.li,{children:["Values are stored as ",(0,r.jsx)(t.a,{href:"https://github.com/CosmWasm/cw-storage-plus/blob/v2.0.0/src/item.rs#L53",children:"serialized JSON"}),",\nso you need to flag that as well with ",(0,r.jsx)(t.code,{children:"cw_serde"})," on ",(0,r.jsx)(t.code,{children:"NameRecord"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Don't forget to make the state locally accessible to your project: add the following line to ",(0,r.jsx)(t.code,{children:"src/lib.rs"}),"."]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/lib.rs"',children:" pub mod contract;\n mod error;\n pub mod msg;\n//diff-add \n+ mod state;\n"})}),"\n",(0,r.jsx)(t.h2,{id:"a-new-error",children:"A new error"}),"\n",(0,r.jsxs)(t.p,{children:["Before moving on to the execution code, you can foresee that you will forbid registering a name that is already taken. Add an error for this situation: the value ",(0,r.jsx)(t.code,{children:"NameTaken"})," to the enum ",(0,r.jsx)(t.code,{children:"ContractError"})," in ",(0,r.jsx)(t.code,{children:"src/error.rs"}),":"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/error.rs"',children:' #[derive(Error, Debug)]\n pub enum ContractError {\n #[error("{0}")]\n Std(#[from] StdError),\n//diff-add-start \n+ #[error("Name already taken ({name})")]\n+ NameTaken { name: String },\n//diff-add-end\n }\n'})}),"\n",(0,r.jsxs)(t.p,{children:["Note that it accepts the ",(0,r.jsx)(t.code,{children:"name"})," as a string in order to provide better error formatting."]}),"\n",(0,r.jsx)(t.h2,{id:"the-execute-function",children:"The execute function"}),"\n",(0,r.jsxs)(t.p,{children:["You add the handling in ",(0,r.jsx)(t.code,{children:"src/contract.rs"}),". Adjust the imports:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:"//diff-del\n- use crate::{error::ContractError, msg::InstantiateMsg};\n//diff-add-start\n+ use crate::{\n+ error::ContractError,\n+ msg::{ExecuteMsg, InstantiateMsg},\n+ state::{NameRecord, NAME_RESOLVER},\n+ };\n//diff-add-end\n"})}),"\n",(0,r.jsx)(t.p,{children:"And add two new functions:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:'#[cfg_attr(not(feature = "library"), entry_point)]\npub fn execute(deps: DepsMut, _: Env, info: MessageInfo, msg: ExecuteMsg) -> ContractResult {\n match msg {\n ExecuteMsg::Register { name } => execute_register(deps, info, name),\n }\n}\n\nfn execute_register(deps: DepsMut, info: MessageInfo, name: String) -> ContractResult {\n let key = name.as_bytes();\n let record = NameRecord { owner: info.sender };\n\n if NAME_RESOLVER.has(deps.storage, key) {\n return Err(ContractError::NameTaken { name });\n }\n\n NAME_RESOLVER.save(deps.storage, key, &record)?;\n\n Ok(Response::default())\n}\n'})}),"\n",(0,r.jsx)(t.p,{children:"Note how:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["The ",(0,r.jsx)(t.code,{children:"execute"})," function only cares to dispatch the messages according to their variant.\nThis is the ",(0,r.jsx)(t.a,{href:"https://docs.cosmwasm.com/core/conventions/enum-dispatch",children:"conventional way"})," of handling execution in CosmWasm,\nso that the function body's size remains manageable as more message variants are added."]}),"\n",(0,r.jsxs)(t.li,{children:["The ",(0,r.jsx)(t.code,{children:"execute_register"})," is where the proper implementation of ",(0,r.jsx)(t.code,{children:"Register"})," is handled."]}),"\n",(0,r.jsx)(t.li,{children:"The implementation is quite typical, you only save to storage if there are no pre-existing values."}),"\n",(0,r.jsxs)(t.li,{children:["The sender information is found in ",(0,r.jsx)(t.code,{children:"info: MessageInfo"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"NAME_RESOLVER"})," has functions named ",(0,r.jsx)(t.code,{children:"has"})," and ",(0,r.jsx)(t.code,{children:"save"}),", where the function parameter is ",(0,r.jsx)(t.code,{children:"deps.storage"}),".\nThis is the CosmWasm way, and it may look like an inversion of responsibility if you come from other platforms."]}),"\n"]}),"\n",(0,r.jsx)(t.p,{children:"Your contract can now register names with the sender addresses while rejecting already registered names."}),"\n",(0,r.jsx)(t.admonition,{type:"tip",children:(0,r.jsxs)(t.p,{children:["If you wanted to make it possible for ",(0,r.jsx)(t.em,{children:"someone"})," to register names for someone else,\nyou would need to add an address to the message too. In particular, if you introduced auctions on
1names,\nthe sender would be the auction smart contract, and the address in the message,\ni.e. the owner, would be the auction winner."]})}),"\n",(0,r.jsx)(t.h2,{id:"unit-testing",children:"Unit testing"}),"\n",(0,r.jsx)(t.p,{children:"With a new message and code, it is time to unit test it."}),"\n",(0,r.jsxs)(t.p,{children:["In ",(0,r.jsx)(t.code,{children:"src/contract.rs"}),", add the following:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:'...\n\n #[cfg(test)]\n mod tests {\n//diff-del\n- use crate::msg::InstantiateMsg;\n//diff-add-start\n+ use crate::{\n+ msg::{ExecuteMsg, InstantiateMsg},\n+ state::{NameRecord, NAME_RESOLVER},\n+ };\n//diff-add-end\n use cosmwasm_std::{testing, Addr, Response};\n\n #[test]\n fn test_instantiate() {\n ...\n }\n\n//diff-add-start\n+ #[test]\n+ fn test_execute() {\n+ // Arrange\n+ let mut mocked_deps_mut = testing::mock_dependencies();\n+ let mocked_env = testing::mock_env();\n+ let mocked_addr = Addr::unchecked("addr");\n+ let mocked_msg_info = testing::message_info(&mocked_addr, &[]);\n+ let name = "alice".to_owned();\n+ let execute_msg = ExecuteMsg::Register { name: name.clone() };\n+ \n+ // Act\n+ let contract_result = super::execute(\n+ mocked_deps_mut.as_mut(),\n+ mocked_env,\n+ mocked_msg_info,\n+ execute_msg,\n+ );\n+ \n+ // Assert\n+ assert!(contract_result.is_ok(), "Failed to register alice");\n+ assert_eq!(contract_result.unwrap(), Response::default());\n+ assert!(NAME_RESOLVER.has(mocked_deps_mut.as_ref().storage, name.as_bytes()));\n+ let stored = NAME_RESOLVER.load(mocked_deps_mut.as_ref().storage, name.as_bytes());\n+ assert!(stored.is_ok());\n+ assert_eq!(stored.unwrap(), NameRecord { owner: mocked_addr });\n+ }\n//diff-add-end\n }\n'})}),"\n",(0,r.jsx)(t.p,{children:"Note how:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"You can mock all the elements that go into the call."}),"\n",(0,r.jsx)(t.li,{children:"It tests the return values of the call."}),"\n",(0,r.jsx)(t.li,{children:"It tests that the expected value is found in storage."}),"\n"]}),"\n",(0,r.jsx)(t.p,{children:"After you run the tests, it should print:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-text",children:"...\nrunning 2 tests\ntest contract::tests::test_instantiate ... ok\ntest contract::tests::test_execute ... ok\n...\n"})}),"\n",(0,r.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsx)(t.p,{children:":::info Exercise progression"}),"\n",(0,r.jsxs)(t.p,{children:["At this stage, you should have something similar\nto the ",(0,r.jsx)(t.a,{href:"https://github.com/b9lab/cw-my-nameservice/tree/first-execute-message",children:(0,r.jsx)(t.code,{children:"first-execute-message"})})," branch,\nwith ",(0,r.jsx)(t.a,{href:"https://github.com/b9lab/cw-my-nameservice/compare/first-unit-test..first-execute-message",children:"this"})," as the diff."]}),"\n",(0,r.jsx)(t.p,{children:":::"}),"\n",(0,r.jsx)(t.p,{children:"It is possible to store names and their addresses, but it is not possible to retrieve them,\nother than querying the storage natively. You fix that in the next section."})]})}function m(e={}){let{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},7250(e,t,n){n.d(t,{A:()=>o});var s=n(4848);n(6540);var r=n(4164),a=n(7663);function i({children:e,className:t,hidden:n}){return(0,s.jsx)("div",{role:"tabpanel",className:(0,r.A)("tabItem_Ymn6",t),hidden:n,children:e})}function o({children:e,className:t,value:n}){let{selectedValue:r,lazy:c}=(0,a.uc)(),l=n===r;return!l&&c?null:(0,s.jsx)(i,{className:t,hidden:!l,children:e})}},773(e,t,n){n.d(t,{A:()=>h});var s=n(4848);n(6540);var r=n(4164),a=n(8287),i=n(7663),o=n(8584),c=n(9863);function l({className:e}){let{selectedValue:t,selectValue:n,tabValues:a,block:c}=(0,i.uc)(),d=[],{blockElementScrollPositionUntilNextRender:u}
1=(0,o.a_)(),h=e=>{let s=e.currentTarget,r=a[d.indexOf(s)].value;r!==t&&(u(s),n(r))},m=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{let n=d.indexOf(e.currentTarget)+1;t=d[n]??d[0];break}case"ArrowLeft":{let n=d.indexOf(e.currentTarget)-1;t=d[n]??d[d.length-1]}}t?.focus()};return(0,s.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":c},e),children:a.map(({value:e,label:n,attributes:a})=>(0,s.jsx)("li",{role:"tab",tabIndex:t===e?0:-1,"aria-selected":t===e,ref:e=>{d.push(e)},onKeyDown:m,onClick:h,...a,className:(0,r.A)("tabs__item","tabItem_LNqP",a?.className,{"tabs__item--active":t===e}),children:n??e},e))})}function d({children:e}){return(0,s.jsx)("div",{className:"margin-top--md",children:e})}function u({className:e,children:t}){return(0,s.jsxs)("div",{className:(0,r.A)(a.G.tabs.container,"tabs-container","tabList__CuJ"),children:[(0,s.jsx)(l,{className:e}),(0,s.jsx)(d,{children:t})]})}function h(e){let t=(0,c.A)(),n=(0,i.OC)(e);return(0,s.jsx)(i.O_,{value:n,children:(0,s.jsx)(u,{className:e.className,children:(0,i.vT)(e.children)})},String(t))}},7663(e,t,n){n.d(t,{OC:()=>h,O_:()=>p,uc:()=>f,vT:()=>d});var s=n(4848),r=n(6540),a=n(6347),i=n(9989),o=n(6629),c=n(618),l=n(1367);function d(e){return r.Children.toArray(e).filter(e=>"\n"!==e)}function u({value:e,tabValues:t}){return t.some(t=>t.value===e)}function h(e){let t,{defaultValue:n,queryString:s=!1,groupId:d}=e,h=function(e){let{values:t,children:n}=e;return(0,r.useMemo)(()=>{let e=t??r.Children.toArray(n).flatMap(e=>{if(!e)return[];if((0,r.isValidElement)(e)&&function(e){let{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return[e];let t="string"==typeof e.type?e.type:e.type.name;throw Error(`Docusaurus error: Bad <Tabs> child <${t}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop. 2If you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:t,attributes:n,default:s}})=>({value:e,label:t,attributes:n,default:s})),s=(0,c.XI)(e,(e,t)=>e.value===t.value);if(s.length>0)throw Error(`Docusaurus error: Duplicate values "${s.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`);return e},[t,n])}(e),[m,f]=(0,r.useState)(()=>(function({defaultValue:e,tabValues:t}){if(0===t.length)throw Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!u({value:e,tabValues:t}))throw Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${t.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}let n=t.find(e=>e.default)??t[0];if(!n)throw Error("Unexpected error: 0 tabValues");return n.value})({defaultValue:n,tabValues:h})),[p,g]=function({queryString:e=!1,groupId:t}){let n=(0,a.W6)(),s=function({queryString:e=!1,groupId:t}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!t)throw Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:e,groupId:t});return[(0,o.aZ)(s),(0,r.useCallback)(e=>{if(!s)return;let t=new URLSearchParams(n.location.search);t.set(s,e),n.replace({...n.location,search:t.toString()})},[s,n])]}({queryString:s,groupId:d}),[x,j]=function({groupId:e}){let t=e?`docusaurus.tab.${e}`:null,[n,s]=(0,l.Dv)(t);return[n,(0,r.useCallback)(e=>{t&&s.set(e)},[t,s])]}({groupId:d}),b=u({value:t=p??x,tabValues:h})?t:null;return(0,i.A)(()=>{b&&f(b)},[b]),{selectedValue:m,selectValue:(0,r.useCallback)(e=>{if(!u({value:e,tabValues:h}))throw Error(`Can't select invalid tab value=${e}`);f(e),g(e),j(e)},[g,j,h]),tabValues:h,lazy:e.lazy??!1,block:e.block??!1}}let m=(0,r.createContext)(null);function f(){let e=r.useContext(m);if(!e)throw Error("useTabsContext() must be used within a Tabs component");return e}function p(e){return(0,s.jsx)(m.Provider,{value:e.value,children:e.children})}},8453(e,t,n){n.d(t,{R:()=>i,x:()=>o});var s=n(6540);let r={},a=s.createContext(r);function i(e){let t=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(a.Provider,{value:t},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.