Submensajes

Los mensajes se utilizan para interactuar tanto con los módulos SDK como con los contratos inteligentes CW. Dado que los mensajes se ejecutan en forma de "configurar y olvidar", no recibirá una respuesta sobre si la llamada fue exitosa o no.

Sin embargo, obtener el resultado de tu llamada puede resultar muy útil en los siguientes casos:

  • Crear una instancia de un nuevo contrato y obtener la dirección del contrato

  • Ejecutar una acción y afirmar que el resultado fue exitoso (por ejemplo, asegurarse de que se haya transferido una determinada cantidad simbólica a su contrato)

  • Manejar el error de su llamada entre contratos en lugar de revertir la transacción

Para obtener el resultado del mensaje enviado desde su contrato inteligente, deberá enviar un submensaje. Puede leer más sobre la semántica de los submensajes y cómo se ordena la ejecución de los submensajes aquí.

Creando un submensaje

Un submensaje envuelve un CosmosMsg en una estructura SubMsg:

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
}

Puede encontrar el código fuente de la estructura SubMsg aquí.

Ahora, veamos un ejemplo de cómo crear una instancia de un token cw20 usando un submensaje:

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);

Estrategias de respuesta

Los submensajes ofrecen cuatro opciones de respuesta diferentes para que las proporcione el otro contrato:

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,
}

Tenga en cuenta que anteriormente creamos el submensaje usando la abreviatura SubMsg::reply_on_success. Sin embargo, también podemos crear un submensaje y especificar explícitamente la estrategia de respuesta.

let submessage = SubMsg {
    gas_limit: None,
    id: INSTANTIATE_REPLY_ID,
    reply_on: ReplyOn::Success,
    msg: instantiate_tx.into()
}

Manejando una respuesta

Para manejar la respuesta del otro contrato, el contrato que llama debe implementar un nuevo punto de entrada. Aquí hay dos ejemplos de cómo manejar las respuestas:

Crear una instancia de un nuevo contrato

#[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())
}

Manejo de una respuesta de una transferencia de token

#[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())
}

Progagacion de contexto entre contratos.

Para detener los ataques de reentrada, CosmWasm no permite que se almacene el contexto en la memoria del contrato. Hay dos formas de propagar el estado entre contratos:

  • Todos los eventos devueltos por el submensaje se pueden leer en el mensaje de respuesta.

  • Almacenar un estado temporal usando cw_storage_plus::Item y cargarlo en el controlador de respuesta

Last updated