diff --git a/CHANGELOG.md b/CHANGELOG.md index 451055879c..8292e9dfea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,11 +25,16 @@ and this project adheres to - cosmwasm-std: Implement `AsRef<[u8]>` for `Binary` and `HexBinary` ([#1550]). - cosmwasm-std: Allow constructing `SupplyResponse` via a `Default` implementation ([#1552], [#1560]). +- cosmwasm-std: Add `Never` type which cannot be instantiated. This can be used + as the error type for `ibc_packet_receive` or `ibc_packet_ack` to gain + confidence that the implementations never errors and the transaction does not + get reverted. ([#1513]) [#1436]: https://github.com/CosmWasm/cosmwasm/issues/1436 [#1437]: https://github.com/CosmWasm/cosmwasm/issues/1437 [#1481]: https://github.com/CosmWasm/cosmwasm/pull/1481 [#1478]: https://github.com/CosmWasm/cosmwasm/pull/1478 +[#1513]: https://github.com/CosmWasm/cosmwasm/pull/1513 [#1533]: https://github.com/CosmWasm/cosmwasm/pull/1533 [#1550]: https://github.com/CosmWasm/cosmwasm/issues/1550 [#1552]: https://github.com/CosmWasm/cosmwasm/pull/1552 diff --git a/IBC.md b/IBC.md index 41497bdd48..fcefc92ffe 100644 --- a/IBC.md +++ b/IBC.md @@ -264,7 +264,9 @@ pub fn ibc_packet_receive( deps: DepsMut, env: Env, msg: IbcPacketReceiveMsg, -) -> StdResult { } +) -> Result { + // ... +} ``` This is a very special entry point as it has a unique workflow. (Please see the @@ -350,27 +352,33 @@ produced 3 suggestions on how to handle errors and rollbacks _inside [main dispatch loop in `ibc-reflect`](https://github.com/CosmWasm/cosmwasm/blob/cd784cd1148ee395574f3e564f102d0d7b5adcc3/contracts/ibc-reflect/src/contract.rs#L217-L248): ```rust - (|| { - // which local channel did this packet come on - let caller = packet.dest.channel_id; - let msg: PacketMsg = from_slice(&packet.data)?; - match msg { - PacketMsg::Dispatch { msgs } => receive_dispatch(deps, caller, msgs), - PacketMsg::WhoAmI {} => receive_who_am_i(deps, caller), - PacketMsg::Balances {} => receive_balances(deps, caller), - } - })() - .or_else(|e| { - // we try to capture all app-level errors and convert them into - // acknowledgement packets that contain an error code. - let acknowledgement = encode_ibc_error(format!("invalid packet: {}", e)); - Ok(IbcReceiveResponse { - acknowledgement, - submessages: vec![], - messages: vec![], - attributes: vec![], - }) - }) + pub fn ibc_packet_receive( + deps: DepsMut, + _env: Env, + msg: IbcPacketReceiveMsg, + ) -> Result { + (|| { + // which local channel did this packet come on + let caller = packet.dest.channel_id; + let msg: PacketMsg = from_slice(&packet.data)?; + match msg { + PacketMsg::Dispatch { msgs } => receive_dispatch(deps, caller, msgs), + PacketMsg::WhoAmI {} => receive_who_am_i(deps, caller), + PacketMsg::Balances {} => receive_balances(deps, caller), + } + })() + .or_else(|e| { + // we try to capture all app-level errors and convert them into + // acknowledgement packets that contain an error code. + let acknowledgement = encode_ibc_error(format!("invalid packet: {}", e)); + Ok(IbcReceiveResponse { + acknowledgement, + submessages: vec![], + messages: vec![], + attributes: vec![], + }) + }) + } ``` 2. If we modify state with an external call, we need to wrap it in a diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index dca8cbf78c..2284c48457 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ entry_point, from_slice, to_binary, DepsMut, Env, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcMsg, IbcOrder, IbcPacketAckMsg, - IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, StdError, StdResult, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, StdError, StdResult, }; use crate::ibc_msg::{ @@ -95,7 +95,7 @@ pub fn ibc_packet_receive( _deps: DepsMut, _env: Env, _packet: IbcPacketReceiveMsg, -) -> StdResult { +) -> Result { Ok(IbcReceiveResponse::new() .set_ack(b"{}") .add_attribute("action", "ibc_packet_ack")) diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index a43e581461..04750f00ec 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{ entry_point, from_slice, to_binary, wasm_execute, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, Event, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcOrder, IbcPacketAckMsg, - IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, MessageInfo, Order, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, MessageInfo, Never, Order, QueryResponse, Reply, Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, WasmMsg, }; @@ -233,7 +233,7 @@ pub fn ibc_packet_receive( deps: DepsMut, _env: Env, msg: IbcPacketReceiveMsg, -) -> StdResult { +) -> Result { // put this in a closure so we can convert all error responses into acknowledgements (|| { let packet = msg.packet; diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 4a03d3025a..809e87484f 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -15,6 +15,7 @@ mod import_helpers; #[cfg(feature = "iterator")] mod iterator; mod math; +mod never; mod panic; mod query; mod results; @@ -48,6 +49,7 @@ pub use crate::math::{ Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128, Uint256, Uint512, Uint64, }; +pub use crate::never::Never; #[cfg(feature = "cosmwasm_1_1")] pub use crate::query::SupplyResponse; pub use crate::query::{ diff --git a/packages/std/src/never.rs b/packages/std/src/never.rs new file mode 100644 index 0000000000..4b9f6c7b8a --- /dev/null +++ b/packages/std/src/never.rs @@ -0,0 +1,27 @@ +/// Never can never be instantiated. This can be used in places +/// where we want to ensure that no error is returned, such as +/// the `ibc_packet_receive` entry point. +/// +/// In contrast to `Empty`, this does not have a JSON schema +/// and cannot be used for message and query types. +/// +/// Once the ! type is stable, this is not needed anymore. +/// See . +pub enum Never {} + +// The Debug implementation is needed to allow the use of `Result::unwrap`. +impl core::fmt::Debug for Never { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Unreachable because no instance of Never can exist + match *self {} + } +} + +// The Display implementation is needed to fulfill the ToString requirement of +// entry point errors: `Result, E>` with `E: ToString`. +impl core::fmt::Display for Never { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Unreachable because no instance of Never can exist + match *self {} + } +}