diff --git a/CHANGELOG.md b/CHANGELOG.md index dee2f4bbac..fb388b2301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,12 @@ and this project adheres to (eg. multisig) be the admin and migrate another contract ([#768]) - cosmwasm-std: Added optional `system` entry point that can only be called by native (blockchain) modules to expose admin functionality if desired. ([#793]) +- cosmwasm-std: Add extra field `submessages` to `Response`, such that you can + get a callback from these messages after their execution (success or failure). + ([#796]) +- cosmwasm-std: Added `reply` entry point that will receive all + callbacks from submessages dispatched by this contract. This is only required + if contract returns "submessages" (above). ([#796]) [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 [#706]: https://github.com/CosmWasm/cosmwasm/pull/706 @@ -53,6 +59,7 @@ and this project adheres to [#716]: https://github.com/CosmWasm/cosmwasm/pull/716 [#768]: https://github.com/CosmWasm/cosmwasm/pull/768 [#793]: https://github.com/CosmWasm/cosmwasm/pull/793 +[#796]: https://github.com/CosmWasm/cosmwasm/pull/796 ### Changed diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index 14d77b1634..c88a8778da 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -46,6 +46,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult let data_msg = format!("burnt {} keys", count).into_bytes(); Ok(Response { + submessages: vec![], messages: vec![send.into()], attributes: vec![attr("action", "burn"), attr("payout", msg.payout)], data: Some(data_msg.into()), diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index d2b346ddc9..6f8fce9220 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -18,6 +18,7 @@ pub fn init(deps: DepsMut, _env: Env, info: MessageInfo, _msg: InitMsg) -> StdRe Ok(Response { data: None, + submessages: vec![], messages: vec![], attributes: vec![attr("action", "init")], }) @@ -54,6 +55,7 @@ pub fn handle_update_admin( config(deps.storage).save(&cfg)?; Ok(Response { + submessages: vec![], messages: vec![], attributes: vec![ attr("action", "handle_update_admin"), @@ -88,6 +90,7 @@ pub fn handle_send_msgs( }; Ok(Response { + submessages: vec![], messages: vec![msg.into()], attributes: vec![attr("action", "handle_send_msgs")], data: None, @@ -118,6 +121,7 @@ pub fn handle_check_remote_balance( }; Ok(Response { + submessages: vec![], messages: vec![msg.into()], attributes: vec![attr("action", "handle_check_remote_balance")], data: None, @@ -168,6 +172,7 @@ pub fn handle_send_funds( }; Ok(Response { + submessages: vec![], messages: vec![msg.into()], attributes: vec![attr("action", "handle_send_funds")], data: None, diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json index 9df44db999..814fa6353a 100644 --- a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json +++ b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AcknowledgementMsgBalances", - "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", + "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"submessages\":[],\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", "anyOf": [ { "type": "object", diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json b/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json index 303c14672e..416a44e452 100644 --- a/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json +++ b/contracts/ibc-reflect/schema/acknowledgement_msg_dispatch.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AcknowledgementMsgDispatch", - "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", + "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"submessages\":[],\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", "anyOf": [ { "type": "object", diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json index a2a699b019..9052cf688b 100644 --- a/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json +++ b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AcknowledgementMsgWhoAmI", - "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", + "description": "This is the final result type that is created and serialized in a contract for every init/handle/migrate call. The VM then deserializes this type to distinguish between successful and failed executions.\n\nWe use a custom type here instead of Rust's Result because we want to be able to define the serialization, which is a public interface. Every language that compiles to Wasm and runs in the ComsWasm VM needs to create the same JSON representation.\n\n# Examples\n\nSuccess:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let response: Response = Response::default(); let result: ContractResult = ContractResult::Ok(response); assert_eq!(to_vec(&result).unwrap(), br#\"{\"ok\":{\"submessages\":[],\"messages\":[],\"attributes\":[],\"data\":null}}\"#.to_vec()); ```\n\nFailure:\n\n``` # use cosmwasm_std::{to_vec, ContractResult, Response}; let error_msg = String::from(\"Something went wrong\"); let result: ContractResult = ContractResult::Err(error_msg); assert_eq!(to_vec(&result).unwrap(), br#\"{\"error\":\"Something went wrong\"}\"#.to_vec()); ```", "anyOf": [ { "type": "object", diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 389129aa7e..98722862a5 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -23,6 +23,7 @@ pub fn init(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InitMsg) -> StdRe config(deps.storage).save(&cfg)?; Ok(Response { + submessages: vec![], messages: vec![], attributes: vec![attr("action", "init")], data: None, @@ -61,6 +62,7 @@ pub fn handle_init_callback( })?; Ok(Response { + submessages: vec![], messages: vec![], attributes: vec![attr("action", "handle_init_callback")], data: None, diff --git a/contracts/reflect/schema/handle_msg.json b/contracts/reflect/schema/handle_msg.json index ff6724a833..73887a47a7 100644 --- a/contracts/reflect/schema/handle_msg.json +++ b/contracts/reflect/schema/handle_msg.json @@ -24,6 +24,28 @@ } } }, + { + "type": "object", + "required": [ + "reflect_sub_call" + ], + "properties": { + "reflect_sub_call": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/SubMsg_for_CustomMsg" + } + } + } + } + } + }, { "type": "object", "required": [ @@ -471,6 +493,32 @@ } ] }, + "SubMsg_for_CustomMsg": { + "description": "A sub-message that will guarantee a subcall_response callback on success or error Note on error the subcall will revert any partial state changes due to this message, but not revert any state changes in the calling contract (that must be done in the subcall_response entry point)", + "type": "object", + "required": [ + "id", + "msg" + ], + "properties": { + "gas_limit": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msg": { + "$ref": "#/definitions/CosmosMsg_for_CustomMsg" + } + } + }, "Uint128": { "type": "string" }, diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index 913d4f9e3c..e34e1ad7dd 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -76,6 +76,28 @@ } } } + }, + { + "description": "If there was a previous ReflectSubCall with this ID, returns cosmwasm_std::Reply", + "type": "object", + "required": [ + "sub_call_result" + ], + "properties": { + "sub_call_result": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } } ], "definitions": { diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json index 79b81edb1b..b8c691b678 100644 --- a/contracts/reflect/schema/response_for__custom_msg.json +++ b/contracts/reflect/schema/response_for__custom_msg.json @@ -1,11 +1,12 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Response_for_CustomMsg", - "description": "A response of a contract entry point, such as `init`, `handle` or `migrate`.\n\nThis type can be constructed directly at the end of the call. Alternatively a mutable response instance can be created early in the contract's logic and incrementally be updated.\n\n## Examples\n\nDirect:\n\n``` # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, MigrateResponse}; # type InitMsg = (); # use cosmwasm_std::{attr, Response, StdResult};\n\npub fn init( deps: DepsMut, _env: Env, _info: MessageInfo, msg: InitMsg, ) -> StdResult { // ...\n\nOk(Response { messages: vec![], attributes: vec![attr(\"action\", \"init\")], data: None, }) } ```\n\nMutating:\n\n``` # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo, MigrateResponse}; # type InitMsg = (); # type MyError = (); # use cosmwasm_std::Response;\n\npub fn init( deps: DepsMut, _env: Env, info: MessageInfo, msg: InitMsg, ) -> Result { let mut response = Response::new(); // ... response.add_attribute(\"Let the\", \"hacking begin\"); // ... response.add_message(BankMsg::Send { to_address: HumanAddr::from(\"recipient\"), amount: coins(128, \"uint\"), }); response.add_attribute(\"foo\", \"bar\"); // ... response.set_data(Binary::from(b\"the result data\")); Ok(response) } ```", + "description": "A response of a contract entry point, such as `init`, `handle` or `migrate`.\n\nThis type can be constructed directly at the end of the call. Alternatively a mutable response instance can be created early in the contract's logic and incrementally be updated.\n\n## Examples\n\nDirect:\n\n``` # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo}; # type InitMsg = (); # use cosmwasm_std::{attr, Response, StdResult};\n\npub fn init( deps: DepsMut, _env: Env, _info: MessageInfo, msg: InitMsg, ) -> StdResult { // ...\n\nOk(Response { submessages: vec![], messages: vec![], attributes: vec![attr(\"action\", \"init\")], data: None, }) } ```\n\nMutating:\n\n``` # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo}; # type InitMsg = (); # type MyError = (); # use cosmwasm_std::Response;\n\npub fn init( deps: DepsMut, _env: Env, info: MessageInfo, msg: InitMsg, ) -> Result { let mut response = Response::new(); // ... response.add_attribute(\"Let the\", \"hacking begin\"); // ... response.add_message(BankMsg::Send { to_address: HumanAddr::from(\"recipient\"), amount: coins(128, \"uint\"), }); response.add_attribute(\"foo\", \"bar\"); // ... response.set_data(Binary::from(b\"the result data\")); Ok(response) } ```", "type": "object", "required": [ "attributes", - "messages" + "messages", + "submessages" ], "properties": { "attributes": { @@ -26,10 +27,18 @@ ] }, "messages": { + "description": "After any submessages are processed, these are all dispatched in the host blockchain. If they all succeed, then the transaction is committed. If any fail, then the transaction and any local contract state changes are reverted.", "type": "array", "items": { "$ref": "#/definitions/CosmosMsg_for_CustomMsg" } + }, + "submessages": { + "description": "Optional list of \"subcalls\" to make. These will be executed in order (and this contract's subcall_response entry point invoked) *before* any of the \"fire and forget\" messages get executed.", + "type": "array", + "items": { + "$ref": "#/definitions/SubMsg_for_CustomMsg" + } } }, "definitions": { @@ -475,6 +484,32 @@ } ] }, + "SubMsg_for_CustomMsg": { + "description": "A sub-message that will guarantee a subcall_response callback on success or error Note on error the subcall will revert any partial state changes due to this message, but not revert any state changes in the calling contract (that must be done in the subcall_response entry point)", + "type": "object", + "required": [ + "id", + "msg" + ], + "properties": { + "gas_limit": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "msg": { + "$ref": "#/definitions/CosmosMsg_for_CustomMsg" + } + } + }, "Uint128": { "type": "string" }, diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index 365ce6f3ac..96c26476dd 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{ - attr, to_binary, to_vec, Binary, ContractResult, CosmosMsg, Deps, DepsMut, Env, HumanAddr, - MessageInfo, QueryRequest, QueryResponse, Response, StdError, StdResult, SystemResult, WasmMsg, + attr, entry_point, to_binary, to_vec, Binary, ContractResult, CosmosMsg, Deps, DepsMut, Env, + HumanAddr, MessageInfo, QueryRequest, QueryResponse, Reply, Response, StdError, StdResult, + SubMsg, SystemResult, WasmMsg, }; use crate::errors::ReflectError; @@ -8,7 +9,7 @@ use crate::msg::{ CallbackMsg, CapitalizedResponse, ChainResponse, CustomMsg, HandleMsg, InitMsg, OwnerResponse, QueryMsg, RawResponse, SpecialQuery, SpecialResponse, }; -use crate::state::{config, config_read, State}; +use crate::state::{config, config_read, replies, replies_read, State}; pub fn init( deps: DepsMut, @@ -45,6 +46,7 @@ pub fn handle( ) -> Result, ReflectError> { match msg { HandleMsg::ReflectMsg { msgs } => try_reflect(deps, env, info, msgs), + HandleMsg::ReflectSubCall { msgs } => try_reflect_subcall(deps, env, info, msgs), HandleMsg::ChangeOwner { owner } => try_change_owner(deps, env, info, owner), } } @@ -69,6 +71,7 @@ pub fn try_reflect( return Err(ReflectError::MessagesEmpty); } let res = Response { + submessages: vec![], messages: msgs, attributes: vec![attr("action", "reflect")], data: None, @@ -76,6 +79,33 @@ pub fn try_reflect( Ok(res) } +pub fn try_reflect_subcall( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msgs: Vec>, +) -> Result, ReflectError> { + let state = config(deps.storage).load()?; + let sender = deps.api.canonical_address(&info.sender)?; + if sender != state.owner { + return Err(ReflectError::NotCurrentOwner { + expected: state.owner, + actual: sender, + }); + } + + if msgs.is_empty() { + return Err(ReflectError::MessagesEmpty); + } + let res = Response { + submessages: msgs, + messages: vec![], + attributes: vec![attr("action", "reflect_subcall")], + data: None, + }; + Ok(res) +} + pub fn try_change_owner( deps: DepsMut, _env: Env, @@ -100,12 +130,21 @@ pub fn try_change_owner( }) } +/// This just stores the result for future query +#[entry_point] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + let key = msg.id.to_be_bytes(); + replies(deps.storage).save(&key, &msg)?; + Ok(Response::default()) +} + pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Owner {} => to_binary(&query_owner(deps)?), QueryMsg::Capitalized { text } => to_binary(&query_capitalized(deps, text)?), QueryMsg::Chain { request } => to_binary(&query_chain(deps, &request)?), QueryMsg::Raw { contract, key } => to_binary(&query_raw(deps, contract, key)?), + QueryMsg::SubCallResult { id } => to_binary(&query_subcall(deps, id)?), } } @@ -117,6 +156,11 @@ fn query_owner(deps: Deps) -> StdResult { Ok(resp) } +fn query_subcall(deps: Deps, id: u64) -> StdResult { + let key = id.to_be_bytes(); + replies_read(deps.storage).load(&key) +} + fn query_capitalized(deps: Deps, text: String) -> StdResult { let req = SpecialQuery::Capitalized { text }.into(); let response: SpecialResponse = deps.querier.custom_query(&req)?; @@ -153,8 +197,8 @@ mod tests { use crate::testing::mock_dependencies_with_custom_querier; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - coin, coins, from_binary, AllBalanceResponse, Api, BankMsg, BankQuery, Binary, StakingMsg, - StdError, + coin, coins, from_binary, AllBalanceResponse, Api, BankMsg, BankQuery, Binary, + ContractResult, Event, StakingMsg, StdError, SubcallResponse, }; #[test] @@ -404,4 +448,71 @@ mod tests { let inner: SpecialResponse = from_binary(&outer.data).unwrap(); assert_eq!(inner.msg, "pong"); } + + #[test] + fn reflect_subcall() { + let mut deps = mock_dependencies_with_custom_querier(&[]); + + let msg = InitMsg { callback_id: None }; + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let id = 123u64; + let payload = SubMsg { + id, + gas_limit: None, + msg: BankMsg::Send { + to_address: HumanAddr::from("friend"), + amount: coins(1, "token"), + } + .into(), + }; + + let msg = HandleMsg::ReflectSubCall { + msgs: vec![payload.clone()], + }; + let info = mock_info("creator", &[]); + let mut res = handle(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + assert_eq!(1, res.submessages.len()); + let submsg = res.submessages.pop().expect("must have a submessage"); + assert_eq!(payload, submsg); + } + + // this mocks out what happens after reflect_subcall + #[test] + fn reply_and_query() { + let mut deps = mock_dependencies_with_custom_querier(&[]); + + let msg = InitMsg { callback_id: None }; + let info = mock_info("creator", &coins(2, "token")); + let _res = init(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let id = 123u64; + let data = Binary::from(b"foobar"); + let events = vec![Event::new("message", vec![attr("signer", "caller-addr")])]; + let result = ContractResult::Ok(SubcallResponse { + events: events.clone(), + data: Some(data.clone()), + }); + let subcall = Reply { id, result }; + let res = reply(deps.as_mut(), mock_env(), subcall).unwrap(); + assert_eq!(0, res.messages.len()); + + // query for a non-existant id + let qres = query( + deps.as_ref(), + mock_env(), + QueryMsg::SubCallResult { id: 65432 }, + ); + assert!(qres.is_err()); + + // query for the real id + let raw = query(deps.as_ref(), mock_env(), QueryMsg::SubCallResult { id }).unwrap(); + let qres: Reply = from_binary(&raw).unwrap(); + assert_eq!(qres.id, id); + let result = qres.result.unwrap(); + assert_eq!(result.data, Some(data)); + assert_eq!(result.events, events); + } } diff --git a/contracts/reflect/src/msg.rs b/contracts/reflect/src/msg.rs index 1ae4ee7d24..db655f1700 100644 --- a/contracts/reflect/src/msg.rs +++ b/contracts/reflect/src/msg.rs @@ -3,7 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, HumanAddr, QueryRequest}; +use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, HumanAddr, QueryRequest, SubMsg}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InitMsg { @@ -28,6 +28,7 @@ pub enum CallbackMsg { #[serde(rename_all = "snake_case")] pub enum HandleMsg { ReflectMsg { msgs: Vec> }, + ReflectSubCall { msgs: Vec> }, ChangeOwner { owner: HumanAddr }, } @@ -48,6 +49,10 @@ pub enum QueryMsg { contract: HumanAddr, key: Binary, }, + /// If there was a previous ReflectSubCall with this ID, returns cosmwasm_std::Reply + SubCallResult { + id: u64, + }, } // We define a custom struct for each query response diff --git a/contracts/reflect/src/state.rs b/contracts/reflect/src/state.rs index a640ef8715..3a2c71a2fb 100644 --- a/contracts/reflect/src/state.rs +++ b/contracts/reflect/src/state.rs @@ -3,10 +3,14 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{CanonicalAddr, Storage}; -use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; +use cosmwasm_std::{CanonicalAddr, Reply, Storage}; +use cosmwasm_storage::{ + bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, + Singleton, +}; const CONFIG_KEY: &[u8] = b"config"; +const RESULT_PREFIX: &[u8] = b"result"; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct State { @@ -20,3 +24,11 @@ pub fn config(storage: &mut dyn Storage) -> Singleton { pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } + +pub fn replies(storage: &mut dyn Storage) -> Bucket { + bucket(storage, RESULT_PREFIX) +} + +pub fn replies_read(storage: &dyn Storage) -> ReadonlyBucket { + bucket_read(storage, RESULT_PREFIX) +} diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index cd0ba6a520..65fd3224c3 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -18,13 +18,13 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, HumanAddr, Response, - StakingMsg, SystemResult, + attr, coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, Event, HumanAddr, Reply, + Response, StakingMsg, SubMsg, SubcallResponse, SystemResult, }; use cosmwasm_vm::{ testing::{ - handle, init, mock_env, mock_info, mock_instance, mock_instance_options, query, MockApi, - MockQuerier, MockStorage, MOCK_CONTRACT_ADDR, + handle, init, mock_env, mock_info, mock_instance, mock_instance_options, query, reply, + MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR, }, Backend, Instance, }; @@ -185,3 +185,66 @@ fn dispatch_custom_query() { let value: CapitalizedResponse = from_binary(&res).unwrap(); assert_eq!(value.text, "DEMO ONE"); } + +#[test] +fn reflect_subcall() { + let mut deps = mock_instance(WASM, &[]); + + let msg = InitMsg { callback_id: None }; + let info = mock_info("creator", &coins(2, "token")); + let _res: Response = init(&mut deps, mock_env(), info, msg).unwrap(); + + let id = 123u64; + let payload = SubMsg { + id, + gas_limit: None, + msg: BankMsg::Send { + to_address: HumanAddr::from("friend"), + amount: coins(1, "token"), + } + .into(), + }; + + let msg = HandleMsg::ReflectSubCall { + msgs: vec![payload.clone()], + }; + let info = mock_info("creator", &[]); + let mut res: Response = handle(&mut deps, mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + assert_eq!(1, res.submessages.len()); + let submsg = res.submessages.pop().expect("must have a submessage"); + assert_eq!(payload, submsg); +} + +// this mocks out what happens after reflect_subcall +#[test] +fn reply_and_query() { + let mut deps = mock_instance(WASM, &[]); + + let msg = InitMsg { callback_id: None }; + let info = mock_info("creator", &coins(2, "token")); + let _res: Response = init(&mut deps, mock_env(), info, msg).unwrap(); + + let id = 123u64; + let data = Binary::from(b"foobar"); + let events = vec![Event::new("message", vec![attr("signer", "caller-addr")])]; + let result = ContractResult::Ok(SubcallResponse { + events: events.clone(), + data: Some(data.clone()), + }); + let subcall = Reply { id, result }; + let res: Response = reply(&mut deps, mock_env(), subcall).unwrap(); + assert_eq!(0, res.messages.len()); + + // query for a non-existant id + let qres = query(&mut deps, mock_env(), QueryMsg::SubCallResult { id: 65432 }); + assert!(qres.is_err()); + + // query for the real id + let raw = query(&mut deps, mock_env(), QueryMsg::SubCallResult { id }).unwrap(); + let qres: Reply = from_binary(&raw).unwrap(); + assert_eq!(qres.id, id); + let result = qres.result.unwrap(); + assert_eq!(result.data, Some(data)); + assert_eq!(result.events, events); +} diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index e892e82e25..9017091ae3 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -89,6 +89,7 @@ pub fn transfer( })?; let res = Response { + submessages: vec![], messages: vec![], attributes: vec![ attr("action", "transfer"), @@ -169,6 +170,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { // bond them to the validator let res = Response { + submessages: vec![], messages: vec![StakingMsg::Delegate { validator: invest.validator, amount: payment.clone(), @@ -234,6 +236,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St // unbond them let res = Response { + submessages: vec![], messages: vec![StakingMsg::Undelegate { validator: invest.validator, amount: coin(unbond.u128(), &invest.bond_denom), @@ -280,6 +283,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult // transfer tokens to the sender balance.amount = to_send; let res = Response { + submessages: vec![], messages: vec![BankMsg::Send { to_address: info.sender.clone(), amount: vec![balance], @@ -305,6 +309,7 @@ pub fn reinvest(deps: DepsMut, env: Env, _info: MessageInfo) -> StdResult( + reply_fn: &dyn Fn(DepsMut, Env, Reply) -> Result, E>, + env_ptr: u32, + msg_ptr: u32, +) -> u32 +where + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, +{ + let res = _do_reply(reply_fn, env_ptr as *mut Region, msg_ptr as *mut Region); + let v = to_vec(&res).unwrap(); + release_buffer(v) as u32 +} + /// do_query should be wrapped in an external "C" export, containing a contract-specific function as arg /// /// - `M`: message type for request @@ -260,6 +278,25 @@ where system_fn(deps.as_mut(), env, msg).into() } +fn _do_reply( + reply_fn: &dyn Fn(DepsMut, Env, Reply) -> Result, E>, + env_ptr: *mut Region, + msg_ptr: *mut Region, +) -> ContractResult> +where + C: Serialize + Clone + fmt::Debug + PartialEq + JsonSchema, + E: ToString, +{ + let env: Vec = unsafe { consume_region(env_ptr) }; + let msg: Vec = unsafe { consume_region(msg_ptr) }; + + let env: Env = try_into_contract_result!(from_slice(&env)); + let msg: Reply = try_into_contract_result!(from_slice(&msg)); + + let mut deps = make_dependencies(); + reply_fn(deps.as_mut(), env, msg).into() +} + fn _do_query( query_fn: &dyn Fn(Deps, Env, M) -> Result, env_ptr: *mut Region, diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 2c70a36266..9d7bad2833 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -44,7 +44,8 @@ pub use crate::query::{ }; pub use crate::results::{ attr, wasm_execute, wasm_instantiate, Attribute, BankMsg, ContractResult, CosmosMsg, Empty, - QueryResponse, Response, StakingMsg, SystemResult, WasmMsg, + Event, QueryResponse, Reply, Response, StakingMsg, SubMsg, SubcallResponse, SystemResult, + WasmMsg, }; #[allow(deprecated)] pub use crate::results::{Context, HandleResponse, InitResponse, MigrateResponse}; @@ -63,7 +64,7 @@ mod imports; mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines. #[cfg(target_arch = "wasm32")] -pub use crate::exports::{do_handle, do_init, do_migrate, do_query, do_system}; +pub use crate::exports::{do_handle, do_init, do_migrate, do_query, do_reply, do_system}; #[cfg(target_arch = "wasm32")] pub use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage}; diff --git a/packages/std/src/results/context.rs b/packages/std/src/results/context.rs index 54e492c6e3..d9a5e0232d 100644 --- a/packages/std/src/results/context.rs +++ b/packages/std/src/results/context.rs @@ -62,6 +62,8 @@ where { fn from(ctx: Context) -> Self { Response { + /// we do not support submessages here, as it was already deprecated when submessages were added + submessages: vec![], messages: ctx.messages, attributes: ctx.attributes, data: ctx.data, diff --git a/packages/std/src/results/contract_result.rs b/packages/std/src/results/contract_result.rs index 4254964816..94c2210a0f 100644 --- a/packages/std/src/results/contract_result.rs +++ b/packages/std/src/results/contract_result.rs @@ -18,7 +18,7 @@ use std::fmt; /// # use cosmwasm_std::{to_vec, ContractResult, Response}; /// let response: Response = Response::default(); /// let result: ContractResult = ContractResult::Ok(response); -/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"messages":[],"attributes":[],"data":null}}"#.to_vec()); +/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"submessages":[],"messages":[],"attributes":[],"data":null}}"#.to_vec()); /// ``` /// /// Failure: @@ -51,6 +51,14 @@ impl ContractResult { pub fn unwrap(self) -> S { self.into_result().unwrap() } + + pub fn is_ok(&self) -> bool { + matches!(self, ContractResult::Ok(_)) + } + + pub fn is_err(&self) -> bool { + matches!(self, ContractResult::Err(_)) + } } impl ContractResult { @@ -93,7 +101,7 @@ mod tests { let result: ContractResult = ContractResult::Ok(Response::default()); assert_eq!( to_vec(&result).unwrap(), - br#"{"ok":{"messages":[],"attributes":[],"data":null}}"#.to_vec() + br#"{"ok":{"submessages":[],"messages":[],"attributes":[],"data":null}}"#.to_vec() ); let result: ContractResult = ContractResult::Err("broken".to_string()); @@ -109,7 +117,8 @@ mod tests { assert_eq!(result, ContractResult::Ok("foo".to_string())); let result: ContractResult = - from_slice(br#"{"ok":{"messages":[],"attributes":[],"data":null}}"#).unwrap(); + from_slice(br#"{"ok":{"submessages":[],"messages":[],"attributes":[],"data":null}}"#) + .unwrap(); assert_eq!(result, ContractResult::Ok(Response::default())); let result: ContractResult = from_slice(br#"{"error":"broken"}"#).unwrap(); diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs index f12b57ea01..5b1fcb7382 100644 --- a/packages/std/src/results/mod.rs +++ b/packages/std/src/results/mod.rs @@ -7,6 +7,7 @@ mod cosmos_msg; mod empty; mod query; mod response; +mod subcall; mod system_result; pub use attribute::{attr, Attribute}; @@ -17,6 +18,7 @@ pub use cosmos_msg::{wasm_execute, wasm_instantiate, BankMsg, CosmosMsg, Staking pub use empty::Empty; pub use query::QueryResponse; pub use response::Response; +pub use subcall::{Event, Reply, SubMsg, SubcallResponse}; pub use system_result::SystemResult; #[deprecated(since = "0.14.0", note = "Renamed to Response.")] diff --git a/packages/std/src/results/response.rs b/packages/std/src/results/response.rs index ef2cb405de..ca7ade01d1 100644 --- a/packages/std/src/results/response.rs +++ b/packages/std/src/results/response.rs @@ -5,6 +5,7 @@ use std::fmt; use crate::Binary; use super::{Attribute, CosmosMsg, Empty}; +use crate::results::SubMsg; /// A response of a contract entry point, such as `init`, `handle` or `migrate`. /// @@ -17,7 +18,7 @@ use super::{Attribute, CosmosMsg, Empty}; /// Direct: /// /// ``` -/// # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, MigrateResponse}; +/// # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo}; /// # type InitMsg = (); /// # /// use cosmwasm_std::{attr, Response, StdResult}; @@ -31,6 +32,7 @@ use super::{Attribute, CosmosMsg, Empty}; /// // ... /// /// Ok(Response { +/// submessages: vec![], /// messages: vec![], /// attributes: vec![attr("action", "init")], /// data: None, @@ -41,7 +43,7 @@ use super::{Attribute, CosmosMsg, Empty}; /// Mutating: /// /// ``` -/// # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo, MigrateResponse}; +/// # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo}; /// # type InitMsg = (); /// # type MyError = (); /// # @@ -72,6 +74,13 @@ pub struct Response where T: Clone + fmt::Debug + PartialEq + JsonSchema, { + /// Optional list of "subcalls" to make. These will be executed in order + /// (and this contract's subcall_response entry point invoked) + /// *before* any of the "fire and forget" messages get executed. + pub submessages: Vec>, + /// After any submessages are processed, these are all dispatched in the host blockchain. + /// If they all succeed, then the transaction is committed. If any fail, then the transaction + /// and any local contract state changes are reverted. pub messages: Vec>, /// The attributes that will be emitted as part of a "wasm" event pub attributes: Vec, @@ -84,6 +93,7 @@ where { fn default() -> Self { Response { + submessages: vec![], messages: vec![], attributes: vec![], data: None, @@ -110,6 +120,20 @@ where self.messages.push(msg.into()); } + pub fn add_submessage>>( + &mut self, + id: u64, + msg: U, + gas_limit: Option, + ) { + let sub = SubMsg { + id, + msg: msg.into(), + gas_limit, + }; + self.submessages.push(sub); + } + pub fn set_data>(&mut self, data: U) { self.data = Some(data.into()); } @@ -125,6 +149,15 @@ mod tests { #[test] fn can_serialize_and_deserialize_init_response() { let original = Response { + submessages: vec![SubMsg { + id: 12, + msg: BankMsg::Send { + to_address: HumanAddr::from("checker"), + amount: coins(888, "moon"), + } + .into(), + gas_limit: Some(12345u64), + }], messages: vec![BankMsg::Send { to_address: HumanAddr::from("you"), amount: coins(1015, "earth"), diff --git a/packages/std/src/results/subcall.rs b/packages/std/src/results/subcall.rs new file mode 100644 index 0000000000..ba5641ee60 --- /dev/null +++ b/packages/std/src/results/subcall.rs @@ -0,0 +1,53 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::{Binary, ContractResult}; + +use super::{Attribute, CosmosMsg, Empty}; + +/// A sub-message that will guarantee a subcall_response callback on success or error +/// Note on error the subcall will revert any partial state changes due to this message, +/// but not revert any state changes in the calling contract (that must be done in the +/// subcall_response entry point) +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SubMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + pub id: u64, + pub msg: CosmosMsg, + pub gas_limit: Option, +} + +/// The Result object returned to subcall_response. We always get the same id back +/// and then must handle success and error cases ourselves +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Reply { + pub id: u64, + pub result: ContractResult, +} + +/// The information we get back from a successful sub-call, with full sdk events +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SubcallResponse { + pub events: Vec, + pub data: Option, +} + +/// a full sdk event +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Event { + #[serde(rename = "type")] + pub kind: String, + pub attributes: Vec, +} + +impl Event { + pub fn new(kind: &str, attributes: Vec) -> Self { + Event { + kind: kind.to_string(), + attributes, + } + } +} diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index be06cab170..bf4c4810d5 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use std::fmt; use wasmer::Val; -use cosmwasm_std::{ContractResult, Env, MessageInfo, QueryResponse, Response}; +use cosmwasm_std::{ContractResult, Env, MessageInfo, QueryResponse, Reply, Response}; use crate::backend::{BackendApi, Querier, Storage}; use crate::conversion::ref_to_u32; @@ -15,6 +15,7 @@ const MAX_LENGTH_INIT: usize = 100_000; const MAX_LENGTH_HANDLE: usize = 100_000; const MAX_LENGTH_MIGRATE: usize = 100_000; const MAX_LENGTH_SYSTEM: usize = 100_000; +const MAX_LENGTH_SUBCALL_RESPONSE: usize = 100_000; const MAX_LENGTH_QUERY: usize = 100_000; pub fn call_init( @@ -89,6 +90,24 @@ where Ok(result) } +pub fn call_reply( + instance: &mut Instance, + env: &Env, + msg: &Reply, +) -> VmResult>> +where + A: BackendApi + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + fmt::Debug + JsonSchema + PartialEq, +{ + let env = to_vec(env)?; + let msg = to_vec(msg)?; + let data = call_reply_raw(instance, &env, &msg)?; + let result: ContractResult> = from_slice(&data)?; + Ok(result) +} + pub fn call_query( instance: &mut Instance, env: &Env, @@ -178,6 +197,22 @@ where call_raw(instance, "system", &[env, msg], MAX_LENGTH_SYSTEM) } +/// Calls Wasm export "reply" and returns raw data from the contract. +/// The result is length limited to prevent abuse but otherwise unchecked. +pub fn call_reply_raw( + instance: &mut Instance, + env: &[u8], + msg: &[u8], +) -> VmResult> +where + A: BackendApi + 'static, + S: Storage + 'static, + Q: Querier + 'static, +{ + instance.set_storage_readonly(false); + call_raw(instance, "reply", &[env, msg], MAX_LENGTH_SUBCALL_RESPONSE) +} + /// Calls Wasm export "query" and returns raw data from the contract. /// The result is length limited to prevent abuse but otherwise unchecked. pub fn call_query_raw( diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index c8a60316a0..3e0c0f1d71 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -599,7 +599,7 @@ mod tests { let report2 = instance.create_gas_report(); assert_eq!(report2.used_externally, 146); - assert_eq!(report2.used_internally, 51108); + assert_eq!(report2.used_internally, 51584); assert_eq!(report2.limit, LIMIT); assert_eq!( report2.remaining, @@ -798,7 +798,7 @@ mod singlepass_tests { .unwrap(); let init_used = orig_gas - instance.get_gas_left(); - assert_eq!(init_used, 51254); + assert_eq!(init_used, 51730); } #[test] @@ -821,7 +821,7 @@ mod singlepass_tests { .unwrap(); let handle_used = gas_before_handle - instance.get_gas_left(); - assert_eq!(handle_used, 165794); + assert_eq!(handle_used, 166259); } #[test] diff --git a/packages/vm/src/lib.rs b/packages/vm/src/lib.rs index 6880f40760..c44948f105 100644 --- a/packages/vm/src/lib.rs +++ b/packages/vm/src/lib.rs @@ -30,7 +30,7 @@ pub use crate::backend::{ pub use crate::cache::{AnalysisReport, Cache, CacheOptions, Stats}; pub use crate::calls::{ call_handle, call_handle_raw, call_init, call_init_raw, call_migrate, call_migrate_raw, - call_query, call_query_raw, call_system, call_system_raw, + call_query, call_query_raw, call_reply, call_reply_raw, call_system, call_system_raw, }; pub use crate::checksum::Checksum; diff --git a/packages/vm/src/testing/calls.rs b/packages/vm/src/testing/calls.rs index 1f157642ad..12ecd27954 100644 --- a/packages/vm/src/testing/calls.rs +++ b/packages/vm/src/testing/calls.rs @@ -5,9 +5,9 @@ use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; use std::fmt; -use cosmwasm_std::{ContractResult, Env, MessageInfo, QueryResponse, Response}; +use cosmwasm_std::{ContractResult, Env, MessageInfo, QueryResponse, Reply, Response}; -use crate::calls::{call_handle, call_init, call_migrate, call_query, call_system}; +use crate::calls::{call_handle, call_init, call_migrate, call_query, call_reply, call_system}; use crate::instance::Instance; use crate::serde::to_vec; use crate::{BackendApi, Querier, Storage}; @@ -90,6 +90,23 @@ where call_system(instance, &env, &serialized_msg).expect("VM error") } +// reply mimicks the call signature of the smart contracts. +// thus it moves env and msg rather than take them as reference. +// this is inefficient here, but only used in test code +pub fn reply( + instance: &mut Instance, + env: Env, + msg: Reply, +) -> ContractResult> +where + A: BackendApi + 'static, + S: Storage + 'static, + Q: Querier + 'static, + U: DeserializeOwned + Clone + PartialEq + JsonSchema + fmt::Debug, +{ + call_reply(instance, &env, &msg).expect("VM error") +} + // query mimicks the call signature of the smart contracts. // thus it moves env and msg rather than take them as reference. // this is inefficient here, but only used in test code diff --git a/packages/vm/src/testing/mod.rs b/packages/vm/src/testing/mod.rs index 06edcc7bfd..ddaa07211f 100644 --- a/packages/vm/src/testing/mod.rs +++ b/packages/vm/src/testing/mod.rs @@ -7,7 +7,7 @@ mod mock; mod querier; mod storage; -pub use calls::{handle, init, migrate, query, system}; +pub use calls::{handle, init, migrate, query, reply, system}; #[cfg(feature = "stargate")] pub use ibc_calls::{ ibc_channel_close, ibc_channel_connect, ibc_channel_open, ibc_packet_ack, ibc_packet_receive, diff --git a/packages/vm/testdata/hackatom_0.14.wasm b/packages/vm/testdata/hackatom_0.14.wasm index 7bf2e565c7..c34bf184a5 100644 Binary files a/packages/vm/testdata/hackatom_0.14.wasm and b/packages/vm/testdata/hackatom_0.14.wasm differ diff --git a/packages/vm/testdata/ibc_reflect_0.14.wasm b/packages/vm/testdata/ibc_reflect_0.14.wasm index 6fadc78a6a..7e20ef3709 100644 Binary files a/packages/vm/testdata/ibc_reflect_0.14.wasm and b/packages/vm/testdata/ibc_reflect_0.14.wasm differ