Terp Network Docs

3.submessages


title: Submessages

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

On this page