Submessages
Messages are used to interact with both SDK modules and CW smart contracts. Since messages are executed in a 'set-and-forget' manner, you will not receive a response regarding whether the call was successful or not.
However, getting the result of your call can be very useful in the following cases:
- Instantiating a new contract and getting the contract address
- Executing an action and asserting that the result was successful (e.g. making sure that a certain token amount has been transferred to your contract)
- Handling the error from your cross-contract call instead of rolling back the transaction
To get the result of the message sent from your smart contract, you will need to dispatch a submessage. You can read more on the semantics of submessages and how submessage execution is ordered here.
Creating a submessage
A submessage wraps a CosmosMsg in a SubMsg struct:
pub struct SubMsg<T> { pub id: u64, // reply_id that will be used to handle the reply pub msg: CosmosMsg<T>, // message to be sent pub gas_limit: Option<u64>, // gas limit for the submessage pub reply_on: ReplyOn, // a flag to determine when the reply should be sent}
You can find the source code for the SubMsg struct here.
Now, let's look at an example of instantiating a cw20 token using a submessage:
const INSTANTIATE_REPLY_ID = 1u64;// Creating a message to create a new cw20 tokenlet instantiate_tx = WasmMsg::Instantiate { admin: None, code_id: msg.cw20_code_id, msg: to_binary(&Cw20InstantiateMsg { name: "new token".to_string(), symbol: "nToken".to_string(), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: env.contract.address.to_string(), cap: None, }), })?, funds: vec![], label: "".to_string(),};// Creating a submessage that wraps the message abovelet submessage = SubMsg::reply_on_success(instantiate_tx.into(), INSTANTIATE_REPLY_ID);// Creating a response with the submessagelet response = Response::new().add_submessage(submessage);
Reply strategies
Submessages offer four different reply options for the other contract to provide:
pub enum ReplyOn { /// Always perform a callback after SubMsg is processed Always, /// Only callback if SubMsg returned an error, no callback on success case Error, /// Only callback if SubMsg was successful, no callback on error case Success, /// Never make a callback - this is like the original CosmosMsg semantics Never,}
Note that previously we created the submessage using the SubMsg::reply_on_success shorthand. However, we can also create a submessage and explicitly specifying the reply strategy.
let submessage = SubMsg { gas_limit: None, id: INSTANTIATE_REPLY_ID, reply_on: ReplyOn::Success, msg: instantiate_tx.into()}
Handling a reply
In order to handle the reply from the other contract, the calling contract must implement a new entry point. Here are two examples of how to handle the replies:
Instantiating a new contract
#[cfg_attr(not(feature = "library"), entry_point)]pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> { match msg.id { INSTANTIATE_REPLY_ID => handle_instantiate_reply(deps, msg), id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))), }}fn handle_instantiate_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> { // Handle the msg data and save the contract address // See: https://github.com/CosmWasm/cw-plus/blob/main/packages/utils/src/parse_reply.rs let res = parse_reply_instantiate_data(msg)?; // Save res.contract_address Ok(Response::new())}
Handling a reply from a token transfer
#[cfg_attr(not(feature = "library"), entry_point)]pub fn reply(deps: DepsMut, msg: Reply) -> StdResult<Response> { match msg.id { CW20_TRANSFER_REPLY_ID => handle_transfer_reply(deps, msg), id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))), }}fn handle_transfer_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> { // Handle the msg data and save the contract address // See: https://github.com/CosmWasm/cw-plus/blob/main/packages/utils/src/parse_reply.rs let data = msg.result.into_result().map_err(StdError::generic_err); // Search for the transfer event // If there are multiple transfers, you will need to find the right event to handle let transfer_event = msg .events .iter() .find(|e| { e.attributes .iter() .any(|attr| attr.key == "action" && attr.value == "transfer") }) .ok_or_else(|| StdError::generic_err(format!("unable to find transfer action"))?; // Do whatever you want with the attributes in the transfer event // Reference to the full event: https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw20-base/src/contract.rs#L239-L244 Ok(Response::new())}
Propagation of context between contracts
To stop reentrancy attacks, CosmWasm does not allow context to be stored in the contract memory. There are two ways to propagate state between contracts:
- All events returned by the submessage can be read from the Reply message
- Storing a temporary state using cw_storage_plus::Item and loading it into the reply handler