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 token
let 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 above
let submessage = SubMsg::reply_on_success(instantiate_tx.into(), INSTANTIATE_REPLY_ID);
// Creating a response with the submessage
let 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