diff --git a/.circleci/config.yml b/.circleci/config.yml index 538c01d396..492598273f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -695,8 +695,10 @@ jobs: - restore_cache: keys: - cargocache-v2-contract_ibc_reflect-rust:1.74-{{ checksum "Cargo.lock" }} + # TODO: Enable this once 2.1 has been released to crates.io - check_contract: - min_version: "2.0" + min_version: "2.1" + skip_cosmwasm_check: true - save_cache: paths: - /usr/local/cargo/registry diff --git a/CHANGELOG.md b/CHANGELOG.md index da9d50069f..ea6cf8e7ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,8 @@ and this project adheres to - cosmwasm-std: Add `TransferMsgBuilder` to more easily create an `IbcMsg::Transfer` with different kinds of memo values, including IBC Callbacks memo values. ([#2167]) +- cosmwasm-std: Add `IbcMsg::WriteAcknowledgement` for async IBC + acknowledgements ([#2130]) [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983 [#2025]: https://github.com/CosmWasm/cosmwasm/pull/2025 @@ -69,6 +71,7 @@ and this project adheres to [#2120]: https://github.com/CosmWasm/cosmwasm/pull/2120 [#2124]: https://github.com/CosmWasm/cosmwasm/pull/2124 [#2129]: https://github.com/CosmWasm/cosmwasm/pull/2129 +[#2130]: https://github.com/CosmWasm/cosmwasm/pull/2130 [#2166]: https://github.com/CosmWasm/cosmwasm/pull/2166 [#2167]: https://github.com/CosmWasm/cosmwasm/pull/2167 diff --git a/contracts/ibc-reflect/Cargo.toml b/contracts/ibc-reflect/Cargo.toml index 7492c27896..8211ba4dee 100644 --- a/contracts/ibc-reflect/Cargo.toml +++ b/contracts/ibc-reflect/Cargo.toml @@ -30,7 +30,7 @@ cranelift = ["cosmwasm-vm/cranelift"] [dependencies] cosmwasm-schema = { path = "../../packages/schema" } -cosmwasm-std = { path = "../../packages/std", features = ["iterator", "stargate", "cosmwasm_2_0"] } +cosmwasm-std = { path = "../../packages/std", features = ["iterator", "stargate", "cosmwasm_2_1"] } schemars = "0.8.12" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/ibc-reflect/schema/ibc-reflect.json b/contracts/ibc-reflect/schema/ibc-reflect.json index 7b45ee1349..caad432dac 100644 --- a/contracts/ibc-reflect/schema/ibc-reflect.json +++ b/contracts/ibc-reflect/schema/ibc-reflect.json @@ -19,7 +19,74 @@ }, "additionalProperties": false }, - "execute": null, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "async_ack" + ], + "properties": { + "async_ack": { + "type": "object", + "required": [ + "ack", + "channel_id", + "packet_sequence" + ], + "properties": { + "ack": { + "description": "The acknowledgement to send back", + "allOf": [ + { + "$ref": "#/definitions/IbcAcknowledgement" + } + ] + }, + "channel_id": { + "description": "Existing channel where the packet was received", + "type": "string" + }, + "packet_sequence": { + "description": "Sequence number of the packet that was received", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "IbcAcknowledgement": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", diff --git a/contracts/ibc-reflect/schema/ibc/packet_msg.json b/contracts/ibc-reflect/schema/ibc/packet_msg.json index 31374ccadf..3628cb6e2f 100644 --- a/contracts/ibc-reflect/schema/ibc/packet_msg.json +++ b/contracts/ibc-reflect/schema/ibc/packet_msg.json @@ -109,6 +109,19 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "no_ack" + ], + "properties": { + "no_ack": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -390,6 +403,18 @@ } ] }, + "IbcAcknowledgement": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "oneOf": [ @@ -481,6 +506,45 @@ }, "additionalProperties": false }, + { + "description": "Acknowledges a packet that this contract received over IBC. This allows acknowledging a packet that was not acknowledged yet in the `ibc_packet_receive` call.", + "type": "object", + "required": [ + "write_acknowledgement" + ], + "properties": { + "write_acknowledgement": { + "type": "object", + "required": [ + "ack", + "channel_id", + "packet_sequence" + ], + "properties": { + "ack": { + "description": "The acknowledgement to send back", + "allOf": [ + { + "$ref": "#/definitions/IbcAcknowledgement" + } + ] + }, + "channel_id": { + "description": "Existing channel where the packet was received", + "type": "string" + }, + "packet_sequence": { + "description": "Sequence number of the packet that was received", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", "type": "object", diff --git a/contracts/ibc-reflect/schema/raw/execute.json b/contracts/ibc-reflect/schema/raw/execute.json new file mode 100644 index 0000000000..6eabd78e56 --- /dev/null +++ b/contracts/ibc-reflect/schema/raw/execute.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "async_ack" + ], + "properties": { + "async_ack": { + "type": "object", + "required": [ + "ack", + "channel_id", + "packet_sequence" + ], + "properties": { + "ack": { + "description": "The acknowledgement to send back", + "allOf": [ + { + "$ref": "#/definitions/IbcAcknowledgement" + } + ] + }, + "channel_id": { + "description": "Existing channel where the packet was received", + "type": "string" + }, + "packet_sequence": { + "description": "Sequence number of the packet that was received", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "IbcAcknowledgement": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/ibc-reflect/src/bin/schema.rs b/contracts/ibc-reflect/src/bin/schema.rs index 5103b778f8..995d5873c7 100644 --- a/contracts/ibc-reflect/src/bin/schema.rs +++ b/contracts/ibc-reflect/src/bin/schema.rs @@ -4,14 +4,15 @@ use cosmwasm_schema::{export_schema, export_schema_with_title, schema_for, write use cosmwasm_std::Empty; use ibc_reflect::msg::{ - AcknowledgementMsg, BalancesResponse, DispatchResponse, InstantiateMsg, PacketMsg, QueryMsg, - WhoAmIResponse, + AcknowledgementMsg, BalancesResponse, DispatchResponse, ExecuteMsg, InstantiateMsg, PacketMsg, + QueryMsg, WhoAmIResponse, }; fn main() { // Clear & write standard API write_api! { instantiate: InstantiateMsg, + execute: ExecuteMsg, query: QueryMsg, migrate: Empty, } diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 9e7e7ddfe8..2a7c5ad64e 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -1,15 +1,15 @@ use cosmwasm_std::{ entry_point, from_json, to_json_binary, wasm_execute, BankMsg, Binary, CosmosMsg, Deps, - DepsMut, Empty, Env, Event, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, - IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcOrder, IbcPacketAckMsg, - IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, MessageInfo, Never, - QueryResponse, Reply, Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, - WasmMsg, + DepsMut, Empty, Env, Event, Ibc3ChannelOpenResponse, IbcAcknowledgement, IbcBasicResponse, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcMsg, + IbcOrder, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, + MessageInfo, Never, QueryResponse, Reply, Response, StdError, StdResult, SubMsg, + SubMsgResponse, SubMsgResult, WasmMsg, }; use crate::msg::{ AccountInfo, AccountResponse, AcknowledgementMsg, BalancesResponse, DispatchResponse, - InstantiateMsg, ListAccountsResponse, PacketMsg, QueryMsg, ReflectExecuteMsg, + ExecuteMsg, InstantiateMsg, ListAccountsResponse, PacketMsg, QueryMsg, ReflectExecuteMsg, ReturnMsgsResponse, WhoAmIResponse, }; use crate::state::{ @@ -37,6 +37,34 @@ pub fn instantiate( Ok(Response::new().add_attribute("action", "instantiate")) } +#[entry_point] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::AsyncAck { + channel_id, + packet_sequence, + ack, + } => execute_async_ack(channel_id, packet_sequence.u64(), ack), + } +} + +fn execute_async_ack( + channel_id: String, + packet_sequence: u64, + ack: IbcAcknowledgement, +) -> StdResult { + Ok(Response::new().add_message(IbcMsg::WriteAcknowledgement { + channel_id, + packet_sequence, + ack, + })) +} + #[entry_point] pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> StdResult { match (reply.id, reply.result) { @@ -249,6 +277,7 @@ pub fn ibc_packet_receive( PacketMsg::Panic {} => execute_panic(), PacketMsg::ReturnErr { text } => execute_error(text), PacketMsg::ReturnMsgs { msgs } => execute_return_msgs(msgs), + PacketMsg::NoAck {} => Ok(IbcReceiveResponse::without_ack()), } })() .or_else(|e| { @@ -607,7 +636,7 @@ mod tests { // acknowledgement is an error let ack: AcknowledgementMsg = from_json(res.acknowledgement.unwrap()).unwrap(); - assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`, `panic`, `return_err`, `return_msgs`"); + assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`, `panic`, `return_err`, `return_msgs`, `no_ack`"); } #[test] diff --git a/contracts/ibc-reflect/src/msg.rs b/contracts/ibc-reflect/src/msg.rs index 6f72005f1c..65277e1fc8 100644 --- a/contracts/ibc-reflect/src/msg.rs +++ b/contracts/ibc-reflect/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Coin, CosmosMsg}; +use cosmwasm_std::{Coin, CosmosMsg, IbcAcknowledgement, Uint64}; /// Just needs to know the code_id of a reflect contract to spawn sub-accounts #[cw_serde] @@ -7,6 +7,18 @@ pub struct InstantiateMsg { pub reflect_code_id: u64, } +#[cw_serde] +pub enum ExecuteMsg { + AsyncAck { + /// Existing channel where the packet was received + channel_id: String, + /// Sequence number of the packet that was received + packet_sequence: Uint64, + /// The acknowledgement to send back + ack: IbcAcknowledgement, + }, +} + #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { @@ -49,6 +61,7 @@ pub enum PacketMsg { Panic {}, ReturnErr { text: String }, ReturnMsgs { msgs: Vec }, + NoAck {}, } /// A custom acknowledgement type. diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs index ecbb60cb86..d1ce4c386a 100644 --- a/contracts/ibc-reflect/tests/integration.rs +++ b/contracts/ibc-reflect/tests/integration.rs @@ -298,5 +298,5 @@ fn handle_dispatch_packet() { // acknowledgement is an error let ack: AcknowledgementMsg = from_slice(&res.acknowledgement.unwrap(), DESERIALIZATION_LIMIT).unwrap(); - assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`, `panic`, `return_err`, `return_msgs`"); + assert_eq!(ack.unwrap_err(), "invalid packet: Error parsing into type ibc_reflect::msg::PacketMsg: unknown variant `reflect_code_id`, expected one of `dispatch`, `who_am_i`, `balances`, `panic`, `return_err`, `return_msgs`, `no_ack`"); } diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go index e9699c1161..908eed160a 100644 --- a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -10,14 +10,23 @@ type SendPacketMsg struct { Data []byte `json:"data"` Timeout IBCTimeout `json:"timeout"` } +type WriteAcknowledgementMsg struct { + // The acknowledgement to send back + Ack IBCAcknowledgement `json:"ack"` + // Existing channel where the packet was received + ChannelID string `json:"channel_id"` + // Sequence number of the packet that was received + PacketSequence uint64 `json:"packet_sequence"` +} type CloseChannelMsg struct { ChannelID string `json:"channel_id"` } type IBCMsg struct { - Transfer *TransferMsg `json:"transfer,omitempty"` - SendPacket *SendPacketMsg `json:"send_packet,omitempty"` - CloseChannel *CloseChannelMsg `json:"close_channel,omitempty"` + Transfer *TransferMsg `json:"transfer,omitempty"` + SendPacket *SendPacketMsg `json:"send_packet,omitempty"` + WriteAcknowledgement *WriteAcknowledgementMsg `json:"write_acknowledgement,omitempty"` + CloseChannel *CloseChannelMsg `json:"close_channel,omitempty"` } // Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) @@ -26,6 +35,10 @@ type Coin struct { Denom string `json:"denom"` // type, eg. "ATOM" } +type IBCAcknowledgement struct { + Data []byte `json:"data"` +} + // IBCTimeout is the timeout for an IBC packet. At least one of block and timestamp is required. type IBCTimeout struct { Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index 426f3fd35e..10fa4ed30c 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -61,6 +61,17 @@ pub enum IbcMsg { /// when packet times out, measured on remote chain timeout: IbcTimeout, }, + /// Acknowledges a packet that this contract received over IBC. + /// This allows acknowledging a packet that was not acknowledged yet in the `ibc_packet_receive` call. + #[cfg(feature = "cosmwasm_2_1")] + WriteAcknowledgement { + /// Existing channel where the packet was received + channel_id: String, + /// Sequence number of the packet that was received + packet_sequence: u64, + /// The acknowledgement to send back + ack: IbcAcknowledgement, + }, /// This will close an existing channel that is owned by this contract. /// Port is auto-assigned to the contract's IBC port CloseChannel { channel_id: String },