|
| 1 | +--- |
| 2 | +tags: ["ibc"] |
| 3 | +--- |
| 4 | + |
| 5 | +# Ping-Pong contract |
| 6 | + |
| 7 | +This CosmWasm smart contract implements a basic IBCv2 Ping-Pong protocol using custom IBCv2 |
| 8 | +messages. It demonstrates cross-chain communication by sending a `PingPongMsg` back and forth |
| 9 | +between two contracts across IBCv2-compatible clients. |
| 10 | + |
| 11 | +The core idea is to initialize a connection with a "ping" (starting with counter = 1) and increment |
| 12 | +the counter with each received response, sending it back to the origin. The contract handles packet |
| 13 | +receipt, acknowledgement, timeouts, and enforces security checks on packet sends. |
| 14 | + |
| 15 | +```rust template="core" |
| 16 | +use cosmwasm_schema::cw_serde; |
| 17 | +use cosmwasm_std::{ |
| 18 | + entry_point, from_json, to_json_vec, Binary, ContractInfoResponse, DepsMut, Empty, Env, |
| 19 | + Ibc2Msg, Ibc2PacketAckMsg, Ibc2PacketReceiveMsg, Ibc2PacketSendMsg, Ibc2PacketTimeoutMsg, |
| 20 | + Ibc2Payload, IbcBasicResponse, IbcReceiveResponse, MessageInfo, Response, StdAck, StdError, |
| 21 | + StdResult, |
| 22 | +}; |
| 23 | + |
| 24 | +/// Represents a simple IBCv2 ping-pong message containing a counter. |
| 25 | +#[cw_serde] |
| 26 | +pub struct PingPongMsg { |
| 27 | + pub counter: u64, |
| 28 | +} |
| 29 | + |
| 30 | +/// Initialization message for the contract. |
| 31 | +#[cw_serde] |
| 32 | +pub struct ExecuteMsg { |
| 33 | + pub source_client: String, |
| 34 | + pub destination_port: String, |
| 35 | +} |
| 36 | + |
| 37 | +/// Initializes the contract. |
| 38 | +/// |
| 39 | +/// # Arguments |
| 40 | +/// - `_deps`: Mutable dependencies of the contract. |
| 41 | +/// - `_env`: The current blockchain environment. |
| 42 | +/// - `_info`: Message sender information (unused). |
| 43 | +/// - `_msg`: Empty message. |
| 44 | +/// |
| 45 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 46 | +pub fn instantiate( |
| 47 | + _deps: DepsMut, |
| 48 | + _env: Env, |
| 49 | + _info: MessageInfo, |
| 50 | + _msg: Empty, |
| 51 | +) -> StdResult<Response> { |
| 52 | + Ok(Response::default()) |
| 53 | +} |
| 54 | + |
| 55 | +/// Sends the first IBCv2 ping message. |
| 56 | +/// |
| 57 | +/// # Arguments |
| 58 | +/// - `deps`: Mutable dependencies of the contract. |
| 59 | +/// - `env`: The current blockchain environment. |
| 60 | +/// - `_info`: Message sender information (unused). |
| 61 | +/// - `msg`: The execute message containing client and port info. |
| 62 | +/// |
| 63 | +/// # Returns |
| 64 | +/// - `StdResult<Response>`: Result containing a response with the IBCv2 packet to be sent. |
| 65 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 66 | +pub fn execute( |
| 67 | + deps: DepsMut, |
| 68 | + env: Env, |
| 69 | + _info: MessageInfo, |
| 70 | + msg: ExecuteMsg, |
| 71 | +) -> StdResult<Response> { |
| 72 | + let ContractInfoResponse { ibc2_port, .. } = deps |
| 73 | + .querier |
| 74 | + .query_wasm_contract_info(env.contract.address)?; |
| 75 | + let source_port = |
| 76 | + ibc2_port.ok_or(StdError::generic_err("Contract's IBCv2 port ID not found"))?; |
| 77 | + let new_payload = Ibc2Payload::new( |
| 78 | + source_port, |
| 79 | + msg.destination_port, |
| 80 | + "V1".to_owned(), |
| 81 | + "application/json".to_owned(), |
| 82 | + Binary::new(to_json_vec(&PingPongMsg { counter: 1 })?), |
| 83 | + ); |
| 84 | + |
| 85 | + let new_msg = Ibc2Msg::SendPacket { |
| 86 | + source_client: msg.source_client, |
| 87 | + payloads: vec![new_payload], |
| 88 | + timeout: env.block.time.plus_minutes(5_u64), |
| 89 | + }; |
| 90 | + |
| 91 | + Ok(Response::default().add_message(new_msg)) |
| 92 | +} |
| 93 | + |
| 94 | +/// Handles acknowledgements for IBCv2 packets. No action is taken in this implementation. |
| 95 | +/// |
| 96 | +/// # Arguments |
| 97 | +/// - `_deps`: Mutable dependencies of the contract. |
| 98 | +/// - `_env`: The current blockchain environment. |
| 99 | +/// - `_msg`: Acknowledgement message received from the IBC channel. |
| 100 | +/// |
| 101 | +/// # Returns |
| 102 | +/// - `StdResult<IbcBasicResponse>`: Default empty response. |
| 103 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 104 | +pub fn ibc2_packet_ack( |
| 105 | + _deps: DepsMut, |
| 106 | + _env: Env, |
| 107 | + _msg: Ibc2PacketAckMsg, |
| 108 | +) -> StdResult<IbcBasicResponse> { |
| 109 | + // Do nothing |
| 110 | + |
| 111 | + Ok(IbcBasicResponse::default()) |
| 112 | +} |
| 113 | + |
| 114 | +/// Handles the receipt of an IBCv2 packet and responds by incrementing the counter |
| 115 | +/// and sending it back to the source. |
| 116 | +/// |
| 117 | +/// # Arguments |
| 118 | +/// - `_deps`: Mutable dependencies of the contract. |
| 119 | +/// - `env`: The current blockchain environment. |
| 120 | +/// - `msg`: The received IBCv2 packet message. |
| 121 | +/// |
| 122 | +/// # Returns |
| 123 | +/// - `StdResult<IbcReceiveResponse>`: Response including a new IBC packet and a successful ack. |
| 124 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 125 | +pub fn ibc2_packet_receive( |
| 126 | + _deps: DepsMut, |
| 127 | + env: Env, |
| 128 | + msg: Ibc2PacketReceiveMsg, |
| 129 | +) -> StdResult<IbcReceiveResponse> { |
| 130 | + let binary_payload = &msg.payload.value; |
| 131 | + let json_payload: PingPongMsg = from_json(binary_payload)?; |
| 132 | + |
| 133 | + let new_payload = Ibc2Payload::new( |
| 134 | + // Swap the source with destination ports to send the message back to the source contract |
| 135 | + msg.payload.destination_port, |
| 136 | + msg.payload.source_port, |
| 137 | + msg.payload.version, |
| 138 | + msg.payload.encoding, |
| 139 | + Binary::new(to_json_vec(&PingPongMsg { |
| 140 | + counter: json_payload.counter + 1, |
| 141 | + })?), |
| 142 | + ); |
| 143 | + |
| 144 | + let new_msg = Ibc2Msg::SendPacket { |
| 145 | + source_client: msg.source_client, |
| 146 | + payloads: vec![new_payload], |
| 147 | + timeout: env.block.time.plus_minutes(5_u64), |
| 148 | + }; |
| 149 | + |
| 150 | + Ok(IbcReceiveResponse::new(StdAck::success(b"\x01")).add_message(new_msg)) |
| 151 | +} |
| 152 | + |
| 153 | +/// Handles timeouts of previously sent IBC packets. Automatically resends the message |
| 154 | +/// without validation or retry limits. |
| 155 | +/// |
| 156 | +/// # Arguments |
| 157 | +/// - `_deps`: Mutable dependencies of the contract. |
| 158 | +/// - `env`: The current blockchain environment. |
| 159 | +/// - `msg`: The timeout message with the failed payload. |
| 160 | +/// |
| 161 | +/// # Returns |
| 162 | +/// - `StdResult<IbcBasicResponse>`: Response with the resend attempt. |
| 163 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 164 | +pub fn ibc2_packet_timeout( |
| 165 | + _deps: DepsMut, |
| 166 | + env: Env, |
| 167 | + msg: Ibc2PacketTimeoutMsg, |
| 168 | +) -> StdResult<IbcBasicResponse> { |
| 169 | + // Let's resend the message without any check. |
| 170 | + // It'd be good to constrain the number of trials. |
| 171 | + |
| 172 | + let msg = Ibc2Msg::SendPacket { |
| 173 | + source_client: msg.source_client, |
| 174 | + payloads: vec![msg.payload], |
| 175 | + timeout: env.block.time.plus_minutes(5_u64), |
| 176 | + }; |
| 177 | + |
| 178 | + Ok(IbcBasicResponse::default().add_message(msg)) |
| 179 | +} |
| 180 | + |
| 181 | +/// Called when an IBCv2 packet is sent. Validates that the sender is the contract itself. |
| 182 | +/// |
| 183 | +/// # Arguments |
| 184 | +/// - `_deps`: Mutable dependencies of the contract. |
| 185 | +/// - `_env`: The current blockchain environment. |
| 186 | +/// - `msg`: The packet send message. |
| 187 | +/// |
| 188 | +/// # Returns |
| 189 | +/// - `StdResult<IbcBasicResponse>`: Default response if sender is valid, error otherwise. |
| 190 | +#[cfg_attr(not(feature = "library"), entry_point)] |
| 191 | +pub fn ibc2_packet_send( |
| 192 | + _deps: DepsMut, |
| 193 | + _env: Env, |
| 194 | + msg: Ibc2PacketSendMsg, |
| 195 | +) -> StdResult<IbcBasicResponse> { |
| 196 | + if msg.signer != _env.contract.address { |
| 197 | + return Err(StdError::generic_err( |
| 198 | + "Only this contract can send messages from its IBCv2 port ID", |
| 199 | + )); |
| 200 | + } |
| 201 | + Ok(IbcBasicResponse::default()) |
| 202 | +} |
| 203 | +``` |
0 commit comments