1"use strict";(self.webpackChunkcosmwasm_documentation=self.webpackChunkcosmwasm_documentation||[]).push([["4122"],{9862(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>u,frontMatter:()=>a,contentTitle:()=>i,toc:()=>d,assets:()=>c});var s=JSON.parse('{"id":"tutorial/platform/cross-query","title":"First contract query integration","description":"Send a synchronous and read-only query from one smart contract to another.","source":"@site/docs/tutorial/platform/13-cross-query.md","sourceDirName":"tutorial/platform","slug":"/tutorial/platform/cross-query","permalink":"/tutorial/platform/cross-query","draft":false,"unlisted":false,"tags":[],"version":"current","lastUpdatedAt":1763722884000,"sidebarPosition":13,"frontMatter":{"title":"First contract query integration","description":"Send a synchronous and read-only query from one smart contract to another."},"sidebar":"tutorialSidebar","previous":{"title":"First contract integration","permalink":"/tutorial/platform/cross-contract"},"next":{"title":"First contract reply integration","permalink":"/tutorial/platform/contract-reply"}}'),r=t(4848),o=t(8453);let a={title:"First contract query integration",description:"Send a synchronous and read-only query from one smart contract to another."},i="First contract query integration",c={},d=[{value:"The use-case",id:"the-use-case",level:2},{value:"Update <code>execute</code>",id:"update-execute",level:2},{value:"Unit tests",id:"unit-tests",level:2},{value:"Add a mock querier",id:"add-a-mock-querier",level:3},{value:"Update execute",id:"update-execute-1",level:3},{value:"Mocked app tests",id:"mocked-app-tests",level:2},{value:"Update the existing test",id:"update-the-existing-test",level:3},{value:"Add a more relevant test",id:"add-a-more-relevant-test",level:3},{value:"Conclusion",id:"conclusion",level:2}];function l(e){let n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"first-contract-query-integration",children:"First contract query integration"})}),"\n",(0,r.jsxs)(n.p,{children:["In the previous section you had your ",(0,r.jsx)(n.em,{children:"collection manager"})," smart contract send a message to your ",(0,r.jsx)(n.em,{children:"name service"}),"\nsmart contract so that the latter executes something. The ",(0,r.jsx)(n.em,{children:"manager"})," sends this message at the end of its own execution."]}),"\n",(0,r.jsx)(n.p,{children:":::info Exercise progression"}),"\n",(0,r.jsx)(n.p,{children:"If you skipped the previous section, you can just switch:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"my-nameservice"})," project to its\n",(0,r.jsx)(n.a,{href:"https://github.com/b9lab/cw-my-nameservice/tree/add-nft-library",children:(0,r.jsx)(n.code,{children:"add-nft-library"})})," branch."]}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"my-collection-manager"})," project to its\n",(0,r.jsx)(n.a,{href:"https://github.com/b9lab/cw-my-collection-manager/tree/initial-pass-through",children:(0,r.jsx)(n.code,{children:"initial-pass-through"})})," branch."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"And take it from there."}),"\n",(0,r.jsx)(n.p,{children:":::"}),"\n",(0,r.jsx)(n.p,{children:"What if your collection manager wants to query a value from the collection in order to make a decision?\nIt would need to make a synchronous query. This is possible. For that, your collection manager assembles\na query message and sends it synchronously, with the certainty that it all happens in a read-only way."}),"\n",(0,r.jsx)(n.h2,{id:"the-use-case",children:"The use-case"}),"\n",(0,r.jsxs)(n.p,{children:["The NFT library keeps track of how many tokens it has minted. It is possible to query this information with\nthe use of ",(0,r.jsx)(n.a,{href:"https://github.com/public-awesome/cw-nfts/blob/v0.19.0/packages/cw721/src/msg.rs#L191-L193",children:(0,r.jsx)(n.code,{children:"QueryMsg::NumTokens"})}),".\nTo demonstrate its use, your collection manager will emit an event about the current number\nof tokens before passing the message through."]}),"\n",(0,r.jsxs)(n.h2,{id:"update-execute",children:["Update ",(0,r.jsx)(n.code,{children:"execute"})]}),"\n",(0,r.jsxs)(n.p,{children:["It is a matter of preparing the ",(0,r.jsx)(n.code,{children:"NumTokens"})," query and interpreting the result:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:' use crate::{\n ...\n// diff-del\n- msg::{CollectionExecuteMsg, ExecuteMsg, InstantiateMsg},\n// diff-add\n+ msg::{CollectionExecuteMsg, CollectionQueryMsg, ExecuteMsg, InstantiateMsg},\n }\n ...\n use cosmwasm_std::{\n// diff-del\n- to_json_binary, DepsMut, Env, MessageInfo, Response, WasmMsg,\n// diff-add\n+ to_json_binary, DepsMut, Env, Event, MessageInfo, QueryRequest, Response, WasmMsg, WasmQuery,\n };\n// diff-add\n+ use cw721::msg::NumTokensResponse;\n ...\n\n fn execute_pass_through(\n// diff-del\n- _: DepsMut,\n// diff-add\n+ deps: DepsMut,\n ...\n ) -> ContractResult {\n ...\n// diff-add-start\n+ let token_count_result =\n+ deps.querier\n+ .query::<NumTokensResponse>(&QueryRequest::Wasm(WasmQuery::Smart {\n+ contract_addr: collection,\n+ msg: to_json_binary(&CollectionQueryMsg::NumTokens {})?,\n+ }));\n+ let token_count_event = Event::new("my-collection-manager")\n+ .add_attribute("token-count-before", token_count_result?.count.to_string());\n// diff-add-end\n Ok(Response::default()\n .add_message(onward_exec_msg)\n// diff-add\n+ .add_event(token_count_event))\n )\n ...\n }\n'})}),"\n",(0,r.jsx)(n.p,{children:"Note how:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"querier"})," gives read-only access."]}),"\n",(0,r.jsx)(n.li,{children:"The preparing and sending very much looks like what you did with mocked-app tests of the query function."}),"\n",(0,r.jsxs)(n.li,{children:["The event is ",(0,r.jsx)(n.code,{children:"token-count-before"})," to make it clear that it takes place before any action, which may be a minting.\nGiven the design of CosmWasm it is not possible to query after the message from within the ",(0,r.jsx)(n.code,{children:"execute"})," function."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"unit-tests",children:"Unit tests"}),"\n",(0,r.jsxs)(n.p,{children:["Updating the unit test is arduous. Indeed, your default mocked dependencies created with ",(0,r.jsx)(n.code,{children:"testing::mock_dependencies()"}),"\ndo not mock any values on your dummy collection. You have to prepare the mocks yourself."]}
1),"\n",(0,r.jsx)(n.h3,{id:"add-a-mock-querier",children:"Add a mock querier"}),"\n",(0,r.jsxs)(n.p,{children:["The following is inspired form the ",(0,r.jsx)(n.a,{href:"https://github.com/public-awesome/names/blob/v2.2.0/contracts/sg721-name/src/unit_tests.rs#L22-L67",children:"work of Stargaze"}),".\nAdd your own ",(0,r.jsx)(n.code,{children:"MockQuerier"})," which returns predefined responses:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:' ...\n mod tests {\n// diff-del\n- use crate::msg::{CollectionExecuteMsg, ExecuteMsg};\n// diff-add\n+ use crate::msg::{CollectionExecuteMsg, CollectionQueryMsg, ExecuteMsg};\n use cosmwasm_std::{\n// diff-add\n+ from_json,\n// diff-del\n- testing,\n// diff-add\n+ testing::{self, MockApi, MockQuerier, MockStorage},\n// diff-del\n- to_json_binary, Addr, Coin, Response, Uint128, WasmMsg,\n// diff-add-start\n+ to_json_binary, Addr, Coin, ContractResult, Empty, Event, OwnedDeps, Querier,\n+ QuerierResult, QueryRequest, Response, SystemError, SystemResult, Uint128, WasmMsg,\n+ WasmQuery,\n// diff-add-end\n };\n// diff-add-start\n+ use cw721::msg::NumTokensResponse;\n+ use std::marker::PhantomData;\n// diff-add-end\n\n// diff-add-start\n+ pub fn mock_deps(\n+ response: NumTokensResponse,\n+ ) -> OwnedDeps<MockStorage, MockApi, NumTokensMockQuerier, Empty> {\n+ OwnedDeps {\n+ storage: MockStorage::default(),\n+ api: MockApi::default(),\n+ querier: NumTokensMockQuerier::new(MockQuerier::new(&[]), response),\n+ custom_query_type: PhantomData,\n+ }\n+ }\n+ \n+ pub struct NumTokensMockQuerier {\n+ base: MockQuerier,\n+ response: NumTokensResponse,\n+ }\n+ \n+ impl Querier for NumTokensMockQuerier {\n+ fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {\n+ match from_json(bin_request) {\n+ Ok(request) => self.handle_query(&request),\n+ Err(e) => SystemResult::Err(SystemError::InvalidRequest {\n+ error: format!("Parsing query request: {}", e),\n+ request: bin_request.into(),\n+ }),\n+ }\n+ }\n+ }\n+ \n+ impl NumTokensMockQuerier {\n+ pub fn handle_query(&self, request: &QueryRequest<Empty>) -> QuerierResult {\n+ match request {\n+ QueryRequest::Wasm(wasm_query) => match wasm_query {\n+ WasmQuery::Smart {\n+ contract_addr: _,\n+ msg,\n+ } => {\n+ let serialized = from_json::<CollectionQueryMsg>(msg)\n+ .map(|collection_query| match collection_query {\n+ CollectionQueryMsg::NumTokens {} => to_json_binary(&self.response)\n+ .expect("Failed to serialize num tokens response"),\n+ _ => unimplemented!("{:?}", collection_query),\n+ })\n+ .expect("Failed to find serialised type");\n+ SystemResult::Ok(ContractResult::Ok(serialized))\n+ }\n+ _ => unimplemented!("{:?}", wasm_query),\n+ },\n+ _ => self.base.handle_query(request),\n+ }\n+ }\n+ \n+ pub fn new(base: MockQuerier<Empty>, response: NumTokensResponse) -> Self {\n+ NumTokensMockQuerier { base, response }\n+ }\n+ }\n// diff-add-end\n ...\n }\n'})}),"\n",(0,r.jsx)(n.p,{children:"Note that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["You define a brand new ",(0,r.jsx)(n.code,{children:"MockQuerier"}),", one that fits your narrow purpose of returning one predefined response."]}),"\n",(0,r.jsxs)(n.li,{children:["Your ",(0,r.jsx)(n.code,{children:"NumTokensMockQuerier"})," is a simple mock that returns the provided ",(0,r.jsx)(n.code,{children:"NumTokensResponse"}),",\nwithout really verifying that it is called only once."]}),"\n",(0,r.jsxs)(n.li,{children:["The mock querier still fails if it receives a query other than ",(0,r.jsx)(n.code,{children:"NumTokens"}),".\nIf you wanted to handle more queries, you would have to adjust it."]}),"\n",(0,r.jsxs)(n.li,{children:["You also have to redefine the ",(0,r.jsx)(n.code,{children:"mock_deps"})," function because its return value is strongly typed,\nand you need ",(0,r.jsx)(n.code,{children:"OwnedDeps<MockStorage, MockApi, NumTokensMockQuerier, Empty>"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["The content of ",(0,r.jsx)(n.code,{children:"handle_query"})," is long but it is mostly about deserializing, picking and serializing."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"update-execute-1",children:"Update execute"}),"\n",(0,r.jsxs)(n.p,{children:["With this, you need to call ",(0,r.jsx)(n.code,{children:"mock_deps"})," with dummy response values ",(0,r.jsx)(n.code,{children:"count: 3"}),",\nand then update the expected message with the expected event:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="src/contract.rs"',children:' ...\n mod tests {\n ...\n fn test_pass_through() {\
1n // Arrange\n// diff-del\n- let mut mocked_deps_mut = testing::mock_dependencies();\n// diff-add\n+ let mut mocked_deps_mut = mock_deps(NumTokensResponse { count: 3 });\n ...\n let expected_response = Response::default()\n .add_message(WasmMsg::Execute {\n contract_addr: "collection".to_owned(),\n msg: to_json_binary(&inner_msg).expect("Failed to serialize inner message"),\n funds: vec![fund_sent],\n// diff-del\n- });\n// diff-add-start\n+ })\n+ .add_event(\n+ Event::new("my-collection-manager").add_attribute("token-count-before", "3"),\n+ );\n// diff-add-end\n ...\n }\n }\n'})}),"\n",(0,r.jsx)(n.p,{children:"Note that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"The difficult work was creating the mock querier."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"mocked-app-tests",children:"Mocked app tests"}),"\n",(0,r.jsx)(n.p,{children:"In these tests, you test the integration between the collection smart contract and the manager,\nso you do not even need to mock the querier, but instead rely on the mocked app to direct your query as expected."}),"\n",(0,r.jsx)(n.h3,{id:"update-the-existing-test",children:"Update the existing test"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="tests/contract.rs"',children:' ...\n fn test_mint_through() {\n ...\n result.assert_event(&expected_cw721_event);\n// diff-add-start\n+ let expected_manager_event =\n+ Event::new("wasm-my-collection-manager").add_attribute("token-count-before", "0");\n+ result.assert_event(&expected_manager_event);\n// diff-add-end\n let owner_query = CollectionQueryMsg::OwnerOf\n ...\n }\n'})}),"\n",(0,r.jsxs)(n.p,{children:["As expected, there are no tokens when you do your first ",(0,r.jsx)(n.em,{children:"mint"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"add-a-more-relevant-test",children:"Add a more relevant test"}),"\n",(0,r.jsx)(n.p,{children:"To make the tests more meaningful, you also add a test for when two mint commands have been sent."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",metastring:'title="tests/contract.rs"',children:'#[test]\nfn test_mint_num_tokens() {\n // Arrange\n let mut mock_app = App::default();\n let (_, addr_manager) = instantiate_collection_manager(&mut mock_app);\n let (_, addr_collection) = instantiate_nameservice(&mut mock_app, addr_manager.to_string());\n let owner_addr = Addr::unchecked("owner");\n let name_alice = "alice".to_owned();\n let name_bob = "bob".to_owned();\n let sender_addr = Addr::unchecked("sender");\n let register_msg = ExecuteMsg::PassThrough {\n collection: addr_collection.to_string(),\n message: CollectionExecuteMsg::Mint {\n token_id: name_alice.clone(),\n owner: owner_addr.to_string(),\n token_uri: None,\n extension: None,\n },\n };\n let _ = mock_app\n .execute_contract(\n sender_addr.clone(),\n addr_manager.clone(),\n ®ister_msg,\n &[],\n )\n .expect("Failed to pass through the first mint message");\n let register_msg = ExecuteMsg::PassThrough {\n collection: addr_collection.to_string(),\n message: CollectionExecuteMsg::Mint {\n token_id: name_bob.clone(),\n owner: owner_addr.to_string(),\n token_uri: None,\n extension: None,\n },\n };\n\n // Act\n let result = mock_app.execute_contract(\n sender_addr.clone(),\n addr_manager.clone(),\n ®ister_msg,\n &[],\n );\n\n // Assert\n assert!(\n result.is_ok(),\n "Failed to pass through the second mint message"\n );\n let result = result.unwrap();\n let expected_cw721_event = Event::new("wasm")\n .add_attribute("_contract_address", addr_collection.to_string())\n .add_attribute("action", "mint")\n .add_attribute("token_id", name_bob.to_string())\n .add_attribute("owner", owner_addr.to_string());\n result.assert_event(&expected_cw721_event);\n let expected_manager_event =\n Event::new("wasm-my-collection-manager").add_attribute("token-count-before", "1");\n result.assert_event(&expected_manager_event);\n assert_eq!(\n mock_app\n .wrap()\n .query_wasm_smart::<OwnerOfResponse>(\n addr_collection.to_owned(),\n &CollectionQueryMsg::OwnerOf {\n token_id: name_alice.to_string(),\n include_expired: None,\n }\n )\n .expect("Failed to query alice name"),\n OwnerOfResponse {\n owner: owner_addr.to_string(),\n approvals: vec![],\n }\n );\n assert_eq!(\n mock_app\n .wrap()\n .query_wasm_smart::<OwnerOfResponse>(\n addr_collection,\n &CollectionQueryMsg::OwnerOf {\n token_id: name_bob.to_string(),\n include_expired: None,\n }\n )\n .expect("Failed to query bob name"),\n OwnerOfResponse {\n owner: owner_addr.to_string(),\n approvals: vec![],\n }\n );\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Note how:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["You mint both the names ",(0,r.jsx)(n.code,{children:"alice"})," and ",(0,r.jsx)(n.code,{children:"bob"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["The event when minting ",(0,r.jsx)(n.code,{children:"bob"})," has a ",(0,r.jsx)(n.code,{children:"token-count-before"})," of ",(0,r.jsx)(n.code,{children:"1"})," because ",(0,r.jsx)(n.code,{children:"alice"}),", and only ",(0,r.jsx)(n.code,{children:"alice"}),", exists."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsx)(n.p,{children:"Your manager smart contract now:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Queries another smart contract synchronously in a read-only mode."}),"\n",(0,r.jsx)(n.li,{children:"Sends a message to another smart contract asynchronously at the end of its own execution."}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:":::info Exercise progression"}),"\n",(0,r.jsx)(n.p,{children:"At this stage:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"my-nameservice"})," project should still have something similar to the\n",(0,r.jsx)(n.a,{href:"https://github.com/b9lab/cw-my-nameservice/tree/add-nft-library",children:(0,r.jsx)(n.code,{children:"add-nft-library"})})," branch."]}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"my-collection-manager"})," project should have something similar to the\n",(0,r.jsx)(n.a,{href:"https://github.com/b9lab/cw-my-collection-manager/tree/cross-contract-query",children:(0,r.jsx)(n.code,{children:"cross-contract-query"})})," branch,\nwith ",(0,r.jsx)(n.a,{href:"https://github.com/b9lab/cw-my-collection-manager/compare/initial-pass-through..cross-contract-query",children:"this"})," as the diff."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:":::"}),"\n",(0,r.jsx)(n.p,{children:"What if the message to the other smart contract needed a reply? For instance, the message could create a new item,\nwhose ID your smart contract needs to know for future reference."}),"\n",(0,r.jsx)(n.p,{children:"This is the object of the reply mechanism in the next section."})]})}function u(e={}){let{wrapper:n}={...(0,o.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:()=>i});var s=t(6540);let r={},o=s.createContext(r);function a(e){let n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),s.createElement(o.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.