IBCv2Example contract

Ping-Pong contract

This CosmWasm smart contract implements a basic IBCv2 Ping-Pong protocol using custom IBCv2 messages. It demonstrates cross-chain communication by sending a PingPongMsg back and forth between two contracts across IBCv2-compatible clients.

The core idea is to initialize a connection with a “ping” (starting with counter = 1) and increment the counter with each received response, sending it back to the origin. The contract handles packet receipt, acknowledgement, timeouts, and enforces security checks on packet sends.

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
    entry_point, from_json, to_json_vec, Binary, ContractInfoResponse, DepsMut, Empty, Env,
    Ibc2Msg, Ibc2PacketAckMsg, Ibc2PacketReceiveMsg, Ibc2PacketSendMsg, Ibc2PacketTimeoutMsg,
    Ibc2Payload, IbcBasicResponse, IbcReceiveResponse, MessageInfo, Response, StdAck, StdError,
    StdResult,
};
 
/// Represents a simple IBCv2 ping-pong message containing a counter.
#[cw_serde]
pub struct PingPongMsg {
    pub counter: u64,
}
 
/// Initialization message for the contract.
#[cw_serde]
pub struct ExecuteMsg {
    pub source_client: String,
    pub destination_port: String,
}
 
/// Initializes the contract.
///
/// # Arguments
/// - `_deps`: Mutable dependencies of the contract.
/// - `_env`: The current blockchain environment.
/// - `_info`: Message sender information (unused).
/// - `_msg`: Empty message.
///
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    _deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    _msg: Empty,
) -> StdResult<Response> {
    Ok(Response::default())
}
 
/// Sends the first IBCv2 ping message.
///
/// # Arguments
/// - `deps`: Mutable dependencies of the contract.
/// - `env`: The current blockchain environment.
/// - `_info`: Message sender information (unused).
/// - `msg`: The execute message containing client and port info.
///
/// # Returns
/// - `StdResult<Response>`: Result containing a response with the IBCv2 packet to be sent.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    env: Env,
    _info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    let ContractInfoResponse { ibc2_port, .. } = deps
        .querier
        .query_wasm_contract_info(env.contract.address)?;
    let source_port =
        ibc2_port.ok_or(StdError::generic_err("Contract's IBCv2 port ID not found"))?;
    let new_payload = Ibc2Payload::new(
        source_port,
        msg.destination_port,
        "V1".to_owned(),
        "application/json".to_owned(),
        Binary::new(to_json_vec(&PingPongMsg { counter: 1 })?),
    );
 
    let new_msg = Ibc2Msg::SendPacket {
        source_client: msg.source_client,
        payloads: vec![new_payload],
        timeout: env.block.time.plus_minutes(5_u64),
    };
 
    Ok(Response::default().add_message(new_msg))
}
 
/// Handles acknowledgements for IBCv2 packets. No action is taken in this implementation.
///
/// # Arguments
/// - `_deps`: Mutable dependencies of the contract.
/// - `_env`: The current blockchain environment.
/// - `_msg`: Acknowledgement message received from the IBC channel.
///
/// # Returns
/// - `StdResult<IbcBasicResponse>`: Default empty response.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc2_packet_ack(
    _deps: DepsMut,
    _env: Env,
    _msg: Ibc2PacketAckMsg,
) -> StdResult<IbcBasicResponse> {
    // Do nothing
 
    Ok(IbcBasicResponse::default())
}
 
/// Handles the receipt of an IBCv2 packet and responds by incrementing the counter
/// and sending it back to the source.
///
/// # Arguments
/// - `_deps`: Mutable dependencies of the contract.
/// - `env`: The current blockchain environment.
/// - `msg`: The received IBCv2 packet message.
///
/// # Returns
/// - `StdResult<IbcReceiveResponse>`: Response including a new IBC packet and a successful ack.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc2_packet_receive(
    _deps: DepsMut,
    env: Env,
    msg: Ibc2PacketReceiveMsg,
) -> StdResult<IbcReceiveResponse> {
    let binary_payload = &msg.payload.value;
    let json_payload: PingPongMsg = from_json(binary_payload)?;
 
    let new_payload = Ibc2Payload::new(
        // Swap the source with destination ports to send the message back to the source contract
        msg.payload.destination_port,
        msg.payload.source_port,
        msg.payload.version,
        msg.payload.encoding,
        Binary::new(to_json_vec(&PingPongMsg {
            counter: json_payload.counter + 1,
        })?),
    );
 
    let new_msg = Ibc2Msg::SendPacket {
        source_client: msg.source_client,
        payloads: vec![new_payload],
        timeout: env.block.time.plus_minutes(5_u64),
    };
 
    Ok(IbcReceiveResponse::new(StdAck::success(b"\x01")).add_message(new_msg))
}
 
/// Handles timeouts of previously sent IBC packets. Automatically resends the message
/// without validation or retry limits.
///
/// # Arguments
/// - `_deps`: Mutable dependencies of the contract.
/// - `env`: The current blockchain environment.
/// - `msg`: The timeout message with the failed payload.
///
/// # Returns
/// - `StdResult<IbcBasicResponse>`: Response with the resend attempt.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc2_packet_timeout(
    _deps: DepsMut,
    env: Env,
    msg: Ibc2PacketTimeoutMsg,
) -> StdResult<IbcBasicResponse> {
    // Let's resend the message without any check.
    // It'd be good to constrain the number of trials.
 
    let msg = Ibc2Msg::SendPacket {
        source_client: msg.source_client,
        payloads: vec![msg.payload],
        timeout: env.block.time.plus_minutes(5_u64),
    };
 
    Ok(IbcBasicResponse::default().add_message(msg))
}
 
/// Called when an IBCv2 packet is sent. Validates that the sender is the contract itself.
///
/// # Arguments
/// - `_deps`: Mutable dependencies of the contract.
/// - `_env`: The current blockchain environment.
/// - `msg`: The packet send message.
///
/// # Returns
/// - `StdResult<IbcBasicResponse>`: Default response if sender is valid, error otherwise.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc2_packet_send(
    _deps: DepsMut,
    _env: Env,
    msg: Ibc2PacketSendMsg,
) -> StdResult<IbcBasicResponse> {
    if msg.signer != _env.contract.address {
        return Err(StdError::generic_err(
            "Only this contract can send messages from its IBCv2 port ID",
        ));
    }
    Ok(IbcBasicResponse::default())
}