From 4e5f146f12746abf07c00e04938d12e48d8c5120 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 15 Apr 2021 22:56:42 +0200 Subject: [PATCH 01/12] Write overview doc, not yet entrypoints --- IBC.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 IBC.md diff --git a/IBC.md b/IBC.md new file mode 100644 index 0000000000..a3e0195228 --- /dev/null +++ b/IBC.md @@ -0,0 +1,80 @@ +# IBC interfaces for CosmWasm contracts + +If you import `cosmwasm-std` with the `stargate` feature flag, it will expose a +number of IBC-related functionality. This requires that the host chain is +running an IBC-enabled version of `wasmd`, that is `v0.16.0` or higher. You will +get an error when you upload the contract if the chain doesn't support this +functionality. + +## Sending Tokens via ICS20 + +There are two ways to use IBC. The simplest one, available to all contracts, is +simply to send tokens to another chain on a pre-established ICS20 channel. ICS20 +is the protocol that is used to move fungible tokens between Cosmos blockchains. +To this end, we expose a +[new `CosmosMsg::Ibc(IbcMsg::Transfer{})` message variant](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L25-L40) +that works similar to `CosmosMsg::Bank(BankMsg::Send{})`, but with a few extra +fields: + +```rust +pub enum IbcMsg { + /// Sends bank tokens owned by the contract to the given address on another chain. + /// The channel must already be established between the ibctransfer module on this chain + /// and a matching module on the remote chain. + /// We cannot select the port_id, this is whatever the local chain has bound the ibctransfer + /// module to. + Transfer { + /// exisiting channel to send the tokens over + channel_id: String, + /// address on the remote chain to receive these tokens + to_address: String, + /// packet data only supports one coin + /// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20 + amount: Coin, + /// block after which the packet times out. + /// at least one of timeout_block, timeout_timestamp is required + timeout_block: Option, + /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. + /// See https://golang.org/pkg/time/#Time.UnixNano + /// at least one of timeout_block, timeout_timestamp is required + timeout_timestamp: Option, + } +} +``` + +Note the `to_address` is likely not a valid `Addr`, as it uses the bech32 prefix +of the _receiving_ chain. In addition to the info you need in `BankMsg::Send`, +you need to define the `channel` to send upon as well as a timeout specified +either in block height or block time (or both). If the packet is not relayed +before the timeout passes (measured on the receiving chain), you can request +your tokens back. + +## Writing New Protocols + +However, we go beyond simply _using_ existing IBC protocols, and allow you to +_implement_ your own ICS protocols inside the contract. A good example to +understand this is the +[`cw20-ics20` contract](https://github.com/CosmWasm/cosmwasm-plus/tree/v0.6.0-beta1/contracts/cw20-ics20) +included in the `cosmwasm-plus` repo. This contract speaks the `ics20-1` +protocol to an external blockchain just as if it were the `ibctransfer` module +in Go. However, we can implement any logic we want there and even hot-load it on +a running blockchain. + +This particular contract above accepts +[cw20 tokens](https://github.com/CosmWasm/cosmwasm-plus/tree/v0.6.0-beta1/packages/cw20) +and sends those to a remote chain, as well as receiving the tokens back and +releasing the original cw20 token to a new owner. It does not (yet) allow +minting coins originating from the remote chain. I recommend opening up the +source code for that contract and refering to it when you want a concrete +example for anything discussed below. + +In order to enable IBC communication, a contract must expose the following 6 +entry points. Upon detecting such an "IBC-Enabled" contract, the +[wasmd runtime](https://github.com/CosmWasm/wasmd) will automatically bind a +port for this contract (`wasm.`), which allows a relayer to +create channels between this contract and another chain. Once channels are +created, the contract will process all packets and receipts. + +### Channel Lifecycle + +### Packet Lifecycle From 6401dcaa1558a8a3e7f43d8576a834265cdeb944 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 15 Apr 2021 23:45:07 +0200 Subject: [PATCH 02/12] Document Channel lifecycle --- IBC.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/IBC.md b/IBC.md index a3e0195228..2651120d03 100644 --- a/IBC.md +++ b/IBC.md @@ -77,4 +77,109 @@ created, the contract will process all packets and receipts. ### Channel Lifecycle +You should first familiarize yourself with the +[4 step channel handshake protocol](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management) +from the IBC spec. After realizing that it was 2 slight variants of 2 steps, we +simplified the interface for the contracts. Each side will receive 2 calls to +establish a new channel, and returning an error in any of the steps will abort +the handshake. Below we will refer to the chains as A and B - A is where the +handshake initialized at. + +#### Channel Open + +The first step of a handshake on either chain is `ibc_channel_open`, which +combines `ChanOpenInit` and `ChanOpenTry` from the spec. The only valid action +of the contract is to accept the channel or reject it. This is generally based +on the ordering and version in the `IbcChannel` information, but you could +enforce other constraints as well: + +```rust +#[entry_point] +/// enforces ordering and versioning constraints +pub fn ibc_channel_open(deps: DepsMut, env: Env, channel: IbcChannel) -> StdResult<()> { } +``` + +This is the +[IbcChannel structure](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L70-L81) +used heavily in the handshake process: + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct IbcChannel { + pub endpoint: IbcEndpoint, + pub counterparty_endpoint: IbcEndpoint, + pub order: IbcOrder, + pub version: String, + /// CounterpartyVersion can be None when not known this context, yet + pub counterparty_version: Option, + /// The connection upon which this channel was created. If this is a multi-hop + /// channel, we only expose the first hop. + pub connection_id: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct IbcEndpoint { + pub port_id: String, + pub channel_id: String, +} +``` + +Note that neither `counterparty_version` nor `counterparty_endpoint` is set in +`ibc_channel_open` for chain A. Chain B should enforce any +`counterparty_version` constraints in `ibc_channel_open`. (So test if the field +is `None` before enforcing any logic). + +You should save any state related to `counterparty_endpoint` only in +`ibc_channel_connect` when it is fixed. + +#### Channel Connect + +Once both sides have returned `Ok()` to `ibc_channel_open`, we move onto the +second step of the handshake, which is equivalent to `ChanOpenAck` and +`ChanOpenConfirm` from the spec: + +```rust +#[entry_point] +/// once it's established, we may take some setup action +pub fn ibc_channel_connect( + deps: DepsMut, + env: Env, + channel: IbcChannel, +) -> StdResult { } +``` + +At this point, it is expected that the contract updates its internal state and +may return `CosmosMsg` in the `Reponse` to interact with other contracts, just +like in `execute`. + +Once this has been called, you may expect to send and receive any number of +packets with the contract. The packets will only stop once the channel is closed +(which may never happen). + +### Channel Close + +A contract may request to close a channel that belongs to it via the following +`CosmosMsg`: + +```rust +pub enum IbcMsg { + /// This will close an existing channel that is owned by this contract. + /// Port is auto-assigned to the contracts' ibc port + CloseChannel { channel_id: String }, +} +``` + +Once a channel is closed, due to error, our request, or request of the other +side, the following callback is made on the contract, which allows it to take +appropriate cleanup action: + +```rust +#[entry_point] +pub fn ibc_channel_close( + deps: DepsMut, + env: Env, + channel: IbcChannel, +) -> StdResult { } +``` + ### Packet Lifecycle From 50f09c5600c3e443363f2eb346dfae86cc003dce Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 16 Apr 2021 00:06:26 +0200 Subject: [PATCH 03/12] Rough draft of packet lifecycle --- IBC.md | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/IBC.md b/IBC.md index 2651120d03..30ed3a00b2 100644 --- a/IBC.md +++ b/IBC.md @@ -183,3 +183,130 @@ pub fn ibc_channel_close( ``` ### Packet Lifecycle + +Unfortunately the +[IBC spec on Pakcet Lifecycle](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#packet-flow--handling) +is missing all useful diagrams, but it may provide some theoretical background +for this text if you wish to look. + +In short, IBC allows us to send packets from chain A to chain B and get a +response from them. The first step is the contract/module in chain A requesting +to send a packet. This is then relayed to chain B, where it "receives" the +packet and calculates an "acknowledgement" (which may contain a success result +or an error message, as opaque bytes to be interpretted by the sending +contract). The acknowledgement is then relayed back to chain A, completing the +cycle. + +In some cases, the packet may never be delivered, and if it is proven not to be +delivered before the timeout period, this can abort the packet, calling the +"timeout" handler on chain A. In this case, chain A sends and later gets +"timeout". No "receive" nor "acknowledgement" callbacks are ever executed. + +#### Sending a Packet + +In order to send a packet, a contract can simply return `IbcMsg::SendPacket` +along with the channel over which to send the packet (which you saved in +`ibc_channel_connect`), as well as opaque data bytes to be interpreted by the +other side. You must also return a timeout either as block height or block time +of the remote chain, just like in the ICS20 `Transfer` messages above: + +```rust +pub enum IbcMsg { + /// Sends an IBC packet with given data over the existing channel. + /// Data should be encoded in a format defined by the channel version, + /// and the module on the other side should know how to parse this. + SendPacket { + channel_id: String, + data: Binary, + /// block height after which the packet times out. + /// at least one of timeout_block, timeout_timestamp is required + timeout_block: Option, + /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. + /// See https://golang.org/pkg/time/#Time.UnixNano + /// at least one of timeout_block, timeout_timestamp is required + timeout_timestamp: Option, + }, +} +``` + +#### Receiving a Packet + +After a contract on chain A sends a packet, it is generally processed by the +contract on chain B on the other side of the channel. This is done by executing +the following callback on chain B: + +```rust +#[entry_point] +/// we look for a the proper reflect contract to relay to and send the message +/// We cannot return any meaningful response value as we do not know the response value +/// of execution. We just return ok if we dispatched, error if we failed to dispatch +pub fn ibc_packet_receive( + deps: DepsMut, + env: Env, + packet: IbcPacket, +) -> StdResult { } +``` + +Note the different return response here (`IbcReceiveResponse` rather than +`IbcBasicResponse`)? This is because it has an extra field +`acknowledgement: Binary`, which must be filled out. That is the response bytes +that will be returned to the original contract, informing it of failure or +success. + +TODO: explain how to handle this + +TODO: document the default JSON encoding used in ICS20 + +TODO: explain how to handle/parse errors (As part of +https://github.com/CosmWasm/cosmwasm/issues/762) + +#### Receiving an Acknowledgement + +If chain B successfully received the packet (even if the contract returned an +error message), chain A will eventually get an acknowledgement: + +```rust +#[entry_point] +/// never should be called as we do not send packets +pub fn ibc_packet_ack( + deps: DepsMut, + env: Env, + ack: IbcAcknowledgement, +) -> StdResult { } +``` + +TODO: explain the data available + +TODO: explain how to handle this + +#### Handling Timeouts + +If the packet was not received on chain B before the timeout, we can be certain +that it will never be processed there. In such a case, a relayer can return a +timeout proof to cancel the pending packet. In such a case the calling contract +will never get `ibc_packet_ack`, but rather `ibc_packet_timeout`. One of the two +calls will eventually get called for each packet that is sent as long as there +is a functioning relayer. (In the absence of a functioning relayer, it will +never get a response). + +The timeout callback looks like this: + +```rust +#[entry_point] +/// never should be called as we do not send packets +pub fn ibc_packet_timeout( + deps: DepsMut, + env: Env, + packet: IbcPacket, +) -> StdResult {} +``` + +It is generally handled just like the error case in `ibc_packet_ack`, reverting +the state change from sending the packet (eg. if we send tokens over ICS20, both +an ack failure as well as a timeout will return those tokens to the original +sender). + +Note that like `ibc_packet_ack`, we get the original packet we sent, which must +contain all information needed to revert itself. Thus the ICS20 packet contains +the original sender address, even though that is unimportant in the receiving +chain. From 874f8bff9fb113c0f2450d84a00c51da348ab32d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 16 Apr 2021 00:12:46 +0200 Subject: [PATCH 04/12] More info --- IBC.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/IBC.md b/IBC.md index 30ed3a00b2..4208c88ca0 100644 --- a/IBC.md +++ b/IBC.md @@ -253,6 +253,36 @@ Note the different return response here (`IbcReceiveResponse` rather than that will be returned to the original contract, informing it of failure or success. +Here is the +[`IbcPacket` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L129-L146) +that contains all information needed to process the receipt. You can generally +ignore timeout (this is only called if it hasn't yet timed out) and sequence +(which is used by the IBC framework to avoid duplicates). I generally use +`dest.channel_id` like `info.sender` to authenticate the packet, and parse +`data` into a `PacketMsg` structure. After that you can process this more or +less like in `execute`. + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct IbcPacket { + /// The raw data send from the other side in the packet + pub data: Binary, + /// identifies the channel and port on the sending chain. + pub src: IbcEndpoint, + /// identifies the channel and port on the receiving chain. + pub dest: IbcEndpoint, + /// The sequence number of the packet on the given channel + pub sequence: u64, + /// block height after which the packet times out. + /// at least one of timeout_block, timeout_timestamp is required + pub timeout_block: Option, + /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. + /// See https://golang.org/pkg/time/#Time.UnixNano + /// at least one of timeout_block, timeout_timestamp is required + pub timeout_timestamp: Option, +} +``` + TODO: explain how to handle this TODO: document the default JSON encoding used in ICS20 @@ -275,7 +305,20 @@ pub fn ibc_packet_ack( ) -> StdResult { } ``` -TODO: explain the data available +The +[`IbcAcknowledgement` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L148-L152) +contains both the original packet that was sent as well as the acknowledgement +bytes returned from executing the remote contract. You can use the +`original_packet` to map it the proper handler, and parse the `acknowledgement` +there: + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct IbcAcknowledgement { + pub acknowledgement: Binary, + pub original_packet: IbcPacket, +} +``` TODO: explain how to handle this From 0f19bc0c867b11b97d77d2b7373ab20d89663bb2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 16 Apr 2021 12:38:10 +0200 Subject: [PATCH 05/12] Complete docs for current state (pre-762) --- IBC.md | 103 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/IBC.md b/IBC.md index 4208c88ca0..ae5bac2c85 100644 --- a/IBC.md +++ b/IBC.md @@ -126,11 +126,16 @@ pub struct IbcEndpoint { Note that neither `counterparty_version` nor `counterparty_endpoint` is set in `ibc_channel_open` for chain A. Chain B should enforce any -`counterparty_version` constraints in `ibc_channel_open`. (So test if the field -is `None` before enforcing any logic). +`counterparty_version` constraints in `ibc_channel_open`. Chain A must enforce +`counterparty_version` or `counterparty_endpoint` restrictions in +`ibc_channel_connect`. -You should save any state related to `counterparty_endpoint` only in -`ibc_channel_connect` when it is fixed. +(Just test if the `counterparty_version` field is `Some(x)` in both calls and +then enforce the counterparty restrictions if set. That will check these once at +the proper place for both chain A and chain B). + +You should save any state only in `ibc_channel_connect` once the channel has +been approved by the remote side. #### Channel Connect @@ -150,7 +155,10 @@ pub fn ibc_channel_connect( At this point, it is expected that the contract updates its internal state and may return `CosmosMsg` in the `Reponse` to interact with other contracts, just -like in `execute`. +like in `execute`. In particular, you will most likely want to store the local +channel_id (`channel.endpoint.channel_id`) in the contract's storage, so it +knows what open channels it has (and can expose those via queries or maintain +state for each one). Once this has been called, you may expect to send and receive any number of packets with the contract. The packets will only stop once the channel is closed @@ -169,9 +177,9 @@ pub enum IbcMsg { } ``` -Once a channel is closed, due to error, our request, or request of the other -side, the following callback is made on the contract, which allows it to take -appropriate cleanup action: +Once a channel is closed, whether due to an IBC error, at our request, or at the +request of the other side, the following callback is made on the contract, which +allows it to take appropriate cleanup action: ```rust #[entry_point] @@ -229,6 +237,13 @@ pub enum IbcMsg { } ``` +For the content of the `data` field, we recommend that you model it on the +format of `ExecuteMsg` (an enum with serde) and encode it via +`cosmwasm_std::to_binary(&packet_msg)?`. This is the approach for a new protocol +you develop with cosmwasm contracts. If you are working with an existing +protocol, please read their spec and create the proper type along with JSON or +Protobuf encoders for it as the protocol requires. + #### Receiving a Packet After a contract on chain A sends a packet, it is generally processed by the @@ -237,9 +252,6 @@ the following callback on chain B: ```rust #[entry_point] -/// we look for a the proper reflect contract to relay to and send the message -/// We cannot return any meaningful response value as we do not know the response value -/// of execution. We just return ok if we dispatched, error if we failed to dispatch pub fn ibc_packet_receive( deps: DepsMut, env: Env, @@ -251,7 +263,7 @@ Note the different return response here (`IbcReceiveResponse` rather than `IbcBasicResponse`)? This is because it has an extra field `acknowledgement: Binary`, which must be filled out. That is the response bytes that will be returned to the original contract, informing it of failure or -success. +success. (Note: this is vague as it will be refined in the next PR) Here is the [`IbcPacket` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L129-L146) @@ -259,8 +271,12 @@ that contains all information needed to process the receipt. You can generally ignore timeout (this is only called if it hasn't yet timed out) and sequence (which is used by the IBC framework to avoid duplicates). I generally use `dest.channel_id` like `info.sender` to authenticate the packet, and parse -`data` into a `PacketMsg` structure. After that you can process this more or -less like in `execute`. +`data` into a `PacketMsg` structure, using the same encoding rules as we +discussed in the last section. + +After that you can process `PacketMsg` more or less like an `ExecuteMsg`, +including calling into other contracts. The only major difference is that you +must return Acknowledgement bytes in the protocol-specified format ```rust #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -283,13 +299,38 @@ pub struct IbcPacket { } ``` -TODO: explain how to handle this - -TODO: document the default JSON encoding used in ICS20 - TODO: explain how to handle/parse errors (As part of https://github.com/CosmWasm/cosmwasm/issues/762) +##### Standard Acknowledgement Format + +Although the ICS spec leave the actual acknowledgement as opaque bytes, it does +provide a recommendation for the format you can use, allowing contracts to +easily differentiate between success and error (and allow IBC explorers to label +such packets without knowing every protocol). + +It is defined as part of the +[ICS4 - Channel Spec](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/ibc/core/channel/v1/channel.proto#L134-L147). + +```proto +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` + +Although it suggests this is a Protobuf object, the ICS spec doesn't define +whether to encode it as JSON or Protobuf. In the ICS20 implementation, this is +JSON encoded when returned from a contract. Given that, we will consider this +structure, JSON-encoded, to be the "standard" acknowledgement format. + +You can find a +[CosmWasm-compatible definition of this format](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L52-L72) +as part of the `cw20-ics20` contract. + #### Receiving an Acknowledgement If chain B successfully received the packet (even if the contract returned an @@ -309,8 +350,10 @@ The [`IbcAcknowledgement` structure](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/packages/std/src/ibc.rs#L148-L152) contains both the original packet that was sent as well as the acknowledgement bytes returned from executing the remote contract. You can use the -`original_packet` to map it the proper handler, and parse the `acknowledgement` -there: +`original_packet` to +[map it the proper handler](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/contracts/ibc-reflect-send/src/ibc.rs#L114-L138) +(after parsing your custom data format), and parse the `acknowledgement` there, +to determine how to respond: ```rust #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -320,7 +363,18 @@ pub struct IbcAcknowledgement { } ``` -TODO: explain how to handle this +On success, you will want to commit the pending state. For some contracts like +`cw20-ics20`, you accept the tokens before sending the packet, so no need to +commit any more state. On other contracts, you may want to store the data +returned as part of the acknowledgement (like +[storing the remote address after calling "WhoAmI"](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta4/contracts/ibc-reflect-send/src/ibc.rs#L157-L192) +in our simple `ibc-reflect` example. + +On error, you will want to revert any state that was pending based on the +packet. For example, in ics20, if the +[remote chain rejects the packet](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L246), +we must +[return the funds to the original sender](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L291-L317). #### Handling Timeouts @@ -346,8 +400,11 @@ pub fn ibc_packet_timeout( It is generally handled just like the error case in `ibc_packet_ack`, reverting the state change from sending the packet (eg. if we send tokens over ICS20, both -an ack failure as well as a timeout will return those tokens to the original -sender). +[an ack failure](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L246) +as well as +[a timeout](https://github.com/CosmWasm/cosmwasm-plus/blob/v0.6.0-beta1/contracts/cw20-ics20/src/ibc.rs#L258) +will return those tokens to the original sender. In fact they both dispatch to +the same `on_packet_failure` function). Note that like `ibc_packet_ack`, we get the original packet we sent, which must contain all information needed to revert itself. Thus the ICS20 packet contains From a2b7f0a7975b962647ff730431028ed5d37e9104 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 11:35:04 +0200 Subject: [PATCH 06/12] Fix typos --- IBC.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/IBC.md b/IBC.md index ae5bac2c85..6873e9184d 100644 --- a/IBC.md +++ b/IBC.md @@ -2,9 +2,10 @@ If you import `cosmwasm-std` with the `stargate` feature flag, it will expose a number of IBC-related functionality. This requires that the host chain is -running an IBC-enabled version of `wasmd`, that is `v0.16.0` or higher. You will -get an error when you upload the contract if the chain doesn't support this -functionality. +running an IBC-enabled version of +[`x/wasmd`](https://github.com/CosmWasm/wasmd/tree/master/x/wasm), that is +`v0.16.0` or higher. You will get an error when you upload the contract if the +chain doesn't support this functionality. ## Sending Tokens via ICS20 @@ -70,7 +71,7 @@ example for anything discussed below. In order to enable IBC communication, a contract must expose the following 6 entry points. Upon detecting such an "IBC-Enabled" contract, the -[wasmd runtime](https://github.com/CosmWasm/wasmd) will automatically bind a +[`x/wasm` runtime](https://github.com/CosmWasm/wasmd) will automatically bind a port for this contract (`wasm.`), which allows a relayer to create channels between this contract and another chain. Once channels are created, the contract will process all packets and receipts. @@ -167,12 +168,12 @@ packets with the contract. The packets will only stop once the channel is closed ### Channel Close A contract may request to close a channel that belongs to it via the following -`CosmosMsg`: +`CosmosMsg::Ibc`: ```rust pub enum IbcMsg { /// This will close an existing channel that is owned by this contract. - /// Port is auto-assigned to the contracts' ibc port + /// Port is auto-assigned to the contract's IBC port CloseChannel { channel_id: String }, } ``` @@ -201,14 +202,13 @@ In short, IBC allows us to send packets from chain A to chain B and get a response from them. The first step is the contract/module in chain A requesting to send a packet. This is then relayed to chain B, where it "receives" the packet and calculates an "acknowledgement" (which may contain a success result -or an error message, as opaque bytes to be interpretted by the sending -contract). The acknowledgement is then relayed back to chain A, completing the -cycle. +or an error message, as opaque bytes to be interpreted by the sending contract). +The acknowledgement is then relayed back to chain A, completing the cycle. In some cases, the packet may never be delivered, and if it is proven not to be -delivered before the timeout period, this can abort the packet, calling the -"timeout" handler on chain A. In this case, chain A sends and later gets -"timeout". No "receive" nor "acknowledgement" callbacks are ever executed. +delivered before the timeout, this can abort the packet, calling the "timeout" +handler on chain A. In this case, chain A sends and later gets "timeout". No +"receive" nor "acknowledgement" callbacks are ever executed. #### Sending a Packet @@ -276,7 +276,7 @@ discussed in the last section. After that you can process `PacketMsg` more or less like an `ExecuteMsg`, including calling into other contracts. The only major difference is that you -must return Acknowledgement bytes in the protocol-specified format +must return Acknowledgement bytes in the protocol-specified format. ```rust #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] From f99fd1bb02abf53f104af2f8dfdb0420d3f7b112 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 11:39:04 +0200 Subject: [PATCH 07/12] Fix typo in IbcMsg::CloseChannel rustdoc --- contracts/ibc-reflect-send/schema/execute_msg.json | 2 +- contracts/ibc-reflect-send/schema/packet_msg.json | 2 +- contracts/ibc-reflect/schema/packet_msg.json | 2 +- contracts/reflect/schema/execute_msg.json | 2 +- contracts/reflect/schema/response_for__custom_msg.json | 2 +- packages/std/src/ibc.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/ibc-reflect-send/schema/execute_msg.json b/contracts/ibc-reflect-send/schema/execute_msg.json index 0d2661737e..c23bead0d8 100644 --- a/contracts/ibc-reflect-send/schema/execute_msg.json +++ b/contracts/ibc-reflect-send/schema/execute_msg.json @@ -434,7 +434,7 @@ "additionalProperties": false }, { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port", + "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", "required": [ "close_channel" diff --git a/contracts/ibc-reflect-send/schema/packet_msg.json b/contracts/ibc-reflect-send/schema/packet_msg.json index aa42712779..ad96e682b1 100644 --- a/contracts/ibc-reflect-send/schema/packet_msg.json +++ b/contracts/ibc-reflect-send/schema/packet_msg.json @@ -387,7 +387,7 @@ "additionalProperties": false }, { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port", + "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", "required": [ "close_channel" diff --git a/contracts/ibc-reflect/schema/packet_msg.json b/contracts/ibc-reflect/schema/packet_msg.json index 2d11da059f..a919285827 100644 --- a/contracts/ibc-reflect/schema/packet_msg.json +++ b/contracts/ibc-reflect/schema/packet_msg.json @@ -386,7 +386,7 @@ "additionalProperties": false }, { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port", + "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", "required": [ "close_channel" diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json index bb9ce19bb8..9ce71a00b1 100644 --- a/contracts/reflect/schema/execute_msg.json +++ b/contracts/reflect/schema/execute_msg.json @@ -430,7 +430,7 @@ "additionalProperties": false }, { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port", + "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", "required": [ "close_channel" diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json index ee839c6153..9ed98d3f61 100644 --- a/contracts/reflect/schema/response_for__custom_msg.json +++ b/contracts/reflect/schema/response_for__custom_msg.json @@ -418,7 +418,7 @@ "additionalProperties": false }, { - "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contracts' ibc port", + "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", "required": [ "close_channel" diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index 1dd5a6ceb3..898f165793 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -53,7 +53,7 @@ pub enum IbcMsg { timeout_timestamp: Option, }, /// This will close an existing channel that is owned by this contract. - /// Port is auto-assigned to the contracts' ibc port + /// Port is auto-assigned to the contract's IBC port CloseChannel { channel_id: String }, } From f13fc2ec80aec18d4f91a30df05525a06d38542f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 12:10:55 +0200 Subject: [PATCH 08/12] Update IbcTimeout format for packets --- packages/std/src/ibc.rs | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index 898f165793..d1017f5f37 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -10,6 +10,7 @@ use std::fmt; use crate::binary::Binary; use crate::coins::Coin; use crate::results::{Attribute, CosmosMsg, Empty, SubMsg}; +use crate::types::BlockInfo; /// These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts /// (contracts that directly speak the IBC protocol via 6 entry points) @@ -30,13 +31,8 @@ pub enum IbcMsg { /// packet data only supports one coin /// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20 amount: Coin, - /// block after which the packet times out. - /// at least one of timeout_block, timeout_timestamp is required - timeout_block: Option, - /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. - /// See https://golang.org/pkg/time/#Time.UnixNano - /// at least one of timeout_block, timeout_timestamp is required - timeout_timestamp: Option, + /// when packet times out, measured on remote chain + timeout: IbcTimeout, }, /// Sends an IBC packet with given data over the existing channel. /// Data should be encoded in a format defined by the channel version, @@ -44,13 +40,8 @@ pub enum IbcMsg { SendPacket { channel_id: String, data: Binary, - /// block height after which the packet times out. - /// at least one of timeout_block, timeout_timestamp is required - timeout_block: Option, - /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. - /// See https://golang.org/pkg/time/#Time.UnixNano - /// at least one of timeout_block, timeout_timestamp is required - timeout_timestamp: Option, + /// when packet times out, measured on remote chain + timeout: IbcTimeout, }, /// This will close an existing channel that is owned by this contract. /// Port is auto-assigned to the contract's IBC port @@ -63,6 +54,29 @@ pub struct IbcEndpoint { pub channel_id: String, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IbcTimeout { + /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out + /// (measured on the remote chain) + /// See https://golang.org/pkg/time/#Time.UnixNano + TimestampNanos(u64), + /// block after which the packet times out (measured on remote chain) + Block(IbcTimeoutBlock), + Both { + timestamp_nanos: u64, + block: IbcTimeoutBlock, + }, +} + +impl IbcTimeout { + pub fn in_secs(block: &BlockInfo, secs: u64) -> Self { + let secs = block.time + secs; + let nanos = secs * 1_000_000_000 + block.time_nanos; + IbcTimeout::TimestampNanos(nanos) + } +} + // These are various messages used in the callbacks /// IbcChannel defines all information on a channel. @@ -136,6 +150,8 @@ pub struct IbcPacket { pub dest: IbcEndpoint, /// The sequence number of the packet on the given channel pub sequence: u64, + // TODO: use IbcTimeout here as well? I doubt this is easier to parse and this + // is data coming from SDK -> contract /// block height after which the packet times out. /// at least one of timeout_block, timeout_timestamp is required pub timeout_block: Option, @@ -239,11 +255,10 @@ mod tests { channel_id: "channel-123".to_string(), to_address: "my-special-addr".into(), amount: Coin::new(12345678, "uatom"), - timeout_block: None, - timeout_timestamp: Some(1234567890), + timeout: IbcTimeout::TimestampNanos(1234567890), }; let encoded = to_string(&msg).unwrap(); - let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout_block":null,"timeout_timestamp":1234567890}}"#; + let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"timestamp_nanos":1234567890}}}"#; assert_eq!(encoded.as_str(), expected); } From 298a4216577d5aae60de1b11ab453597526a1af1 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 12:21:00 +0200 Subject: [PATCH 09/12] Update IbcTimeout usage in contracts --- .../ibc-reflect-send/schema/execute_msg.json | 102 ++++++++++++------ .../ibc-reflect-send/schema/packet_msg.json | 102 ++++++++++++------ contracts/ibc-reflect-send/src/contract.rs | 15 ++- contracts/ibc-reflect-send/src/ibc.rs | 21 ++-- .../ibc-reflect-send/tests/integration.rs | 8 +- contracts/ibc-reflect/schema/packet_msg.json | 102 ++++++++++++------ packages/std/src/lib.rs | 2 +- 7 files changed, 223 insertions(+), 129 deletions(-) diff --git a/contracts/ibc-reflect-send/schema/execute_msg.json b/contracts/ibc-reflect-send/schema/execute_msg.json index c23bead0d8..656c70add2 100644 --- a/contracts/ibc-reflect-send/schema/execute_msg.json +++ b/contracts/ibc-reflect-send/schema/execute_msg.json @@ -344,6 +344,7 @@ "required": [ "amount", "channel_id", + "timeout", "to_address" ], "properties": { @@ -359,26 +360,14 @@ "description": "exisiting channel to send the tokens over", "type": "string" }, - "timeout_block": { - "description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, "to_address": { "description": "address on the remote chain to receive these tokens", "type": "string" @@ -399,7 +388,8 @@ "type": "object", "required": [ "channel_id", - "data" + "data", + "timeout" ], "properties": { "channel_id": { @@ -408,25 +398,13 @@ "data": { "$ref": "#/definitions/Binary" }, - "timeout_block": { - "description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] - }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 } } } @@ -456,6 +434,64 @@ } ] }, + "IbcTimeout": { + "anyOf": [ + { + "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano", + "type": "object", + "required": [ + "timestamp_nanos" + ], + "properties": { + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "block after which the packet times out (measured on remote chain)", + "type": "object", + "required": [ + "block" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "both" + ], + "properties": { + "both": { + "type": "object", + "required": [ + "block", + "timestamp_nanos" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "IbcTimeoutBlock": { "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", "type": "object", diff --git a/contracts/ibc-reflect-send/schema/packet_msg.json b/contracts/ibc-reflect-send/schema/packet_msg.json index ad96e682b1..06c155270c 100644 --- a/contracts/ibc-reflect-send/schema/packet_msg.json +++ b/contracts/ibc-reflect-send/schema/packet_msg.json @@ -297,6 +297,7 @@ "required": [ "amount", "channel_id", + "timeout", "to_address" ], "properties": { @@ -312,26 +313,14 @@ "description": "exisiting channel to send the tokens over", "type": "string" }, - "timeout_block": { - "description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, "to_address": { "description": "address on the remote chain to receive these tokens", "type": "string" @@ -352,7 +341,8 @@ "type": "object", "required": [ "channel_id", - "data" + "data", + "timeout" ], "properties": { "channel_id": { @@ -361,25 +351,13 @@ "data": { "$ref": "#/definitions/Binary" }, - "timeout_block": { - "description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] - }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 } } } @@ -409,6 +387,64 @@ } ] }, + "IbcTimeout": { + "anyOf": [ + { + "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano", + "type": "object", + "required": [ + "timestamp_nanos" + ], + "properties": { + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "block after which the packet times out (measured on remote chain)", + "type": "object", + "required": [ + "block" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "both" + ], + "properties": { + "both": { + "type": "object", + "required": [ + "block", + "timestamp_nanos" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "IbcTimeoutBlock": { "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", "type": "object", diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index b85e08fb27..f7d8b80999 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{ - attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Order, - QueryResponse, Response, StdError, StdResult, + attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, + Order, QueryResponse, Response, StdError, StdResult, }; -use crate::ibc::build_timeout_timestamp; +use crate::ibc::PACKET_LIFETIME; use crate::ibc_msg::PacketMsg; use crate::msg::{ AccountInfo, AccountResponse, AdminResponse, ExecuteMsg, InstantiateMsg, ListAccountsResponse, @@ -91,8 +91,7 @@ pub fn handle_send_msgs( let msg = IbcMsg::SendPacket { channel_id, data: to_binary(&packet)?, - timeout_block: None, - timeout_timestamp: Some(build_timeout_timestamp(&env.block)), + timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), }; Ok(Response { @@ -122,8 +121,7 @@ pub fn handle_check_remote_balance( let msg = IbcMsg::SendPacket { channel_id, data: to_binary(&packet)?, - timeout_block: None, - timeout_timestamp: Some(build_timeout_timestamp(&env.block)), + timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), }; Ok(Response { @@ -173,8 +171,7 @@ pub fn handle_send_funds( channel_id: transfer_channel_id, to_address: remote_addr, amount, - timeout_block: None, - timeout_timestamp: Some(build_timeout_timestamp(&env.block)), + timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), }; Ok(Response { diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index b15b96387d..874a469459 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{ - attr, entry_point, from_slice, to_binary, BlockInfo, DepsMut, Env, IbcAcknowledgement, - IbcBasicResponse, IbcChannel, IbcMsg, IbcOrder, IbcPacket, IbcReceiveResponse, StdError, - StdResult, + attr, entry_point, from_slice, to_binary, DepsMut, Env, IbcAcknowledgement, IbcBasicResponse, + IbcChannel, IbcMsg, IbcOrder, IbcPacket, IbcReceiveResponse, IbcTimeout, StdError, StdResult, }; use crate::ibc_msg::{ @@ -13,12 +12,7 @@ pub const IBC_VERSION: &str = "ibc-reflect-v1"; // TODO: make configurable? /// packets live one hour -const PACKET_LIFETIME: u64 = 60 * 60; - -pub(crate) fn build_timeout_timestamp(block: &BlockInfo) -> u64 { - let timeout = block.time + PACKET_LIFETIME; - timeout * 1_000_000_000 -} +pub const PACKET_LIFETIME: u64 = 60 * 60; #[entry_point] /// enforces ordering and versioing constraints @@ -64,8 +58,7 @@ pub fn ibc_channel_connect( let msg = IbcMsg::SendPacket { channel_id: channel_id.clone(), data: to_binary(&packet)?, - timeout_block: None, - timeout_timestamp: Some(build_timeout_timestamp(&env.block)), + timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), }; Ok(IbcBasicResponse { @@ -447,14 +440,12 @@ mod tests { channel_id, to_address, amount, - timeout_block, - timeout_timestamp, + timeout, }) => { assert_eq!(transfer_channel_id, channel_id.as_str()); assert_eq!(remote_addr, to_address.as_str()); assert_eq!(&coin(12344, "utrgd"), amount); - assert!(timeout_block.is_none()); - assert!(timeout_timestamp.is_some()); + assert!(matches!(timeout, IbcTimeout::TimestampNanos { .. })); } o => panic!("unexpected message: {:?}", o), } diff --git a/contracts/ibc-reflect-send/tests/integration.rs b/contracts/ibc-reflect-send/tests/integration.rs index a0a375c008..cf512a1265 100644 --- a/contracts/ibc-reflect-send/tests/integration.rs +++ b/contracts/ibc-reflect-send/tests/integration.rs @@ -20,7 +20,7 @@ use cosmwasm_std::testing::{mock_ibc_channel, mock_ibc_packet_ack}; use cosmwasm_std::{ attr, coin, coins, to_binary, BankMsg, CosmosMsg, Empty, IbcAcknowledgement, IbcBasicResponse, - IbcMsg, IbcOrder, Response, + IbcMsg, IbcOrder, IbcTimeout, Response, }; use cosmwasm_vm::testing::{ execute, ibc_channel_connect, ibc_channel_open, ibc_packet_ack, instantiate, mock_env, @@ -234,14 +234,12 @@ fn send_remote_funds() { channel_id, to_address, amount, - timeout_block, - timeout_timestamp, + timeout, }) => { assert_eq!(transfer_channel_id, channel_id.as_str()); assert_eq!(remote_addr, to_address.as_str()); assert_eq!(&coin(12344, "utrgd"), amount); - assert!(timeout_block.is_none()); - assert!(timeout_timestamp.is_some()); + assert!(matches!(timeout, IbcTimeout::TimestampNanos { .. })); } o => panic!("unexpected message: {:?}", o), } diff --git a/contracts/ibc-reflect/schema/packet_msg.json b/contracts/ibc-reflect/schema/packet_msg.json index a919285827..46b6d6225a 100644 --- a/contracts/ibc-reflect/schema/packet_msg.json +++ b/contracts/ibc-reflect/schema/packet_msg.json @@ -296,6 +296,7 @@ "required": [ "amount", "channel_id", + "timeout", "to_address" ], "properties": { @@ -311,26 +312,14 @@ "description": "exisiting channel to send the tokens over", "type": "string" }, - "timeout_block": { - "description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, "to_address": { "description": "address on the remote chain to receive these tokens", "type": "string" @@ -351,7 +340,8 @@ "type": "object", "required": [ "channel_id", - "data" + "data", + "timeout" ], "properties": { "channel_id": { @@ -360,25 +350,13 @@ "data": { "$ref": "#/definitions/Binary" }, - "timeout_block": { - "description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] - }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 } } } @@ -408,6 +386,64 @@ } ] }, + "IbcTimeout": { + "anyOf": [ + { + "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano", + "type": "object", + "required": [ + "timestamp_nanos" + ], + "properties": { + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "block after which the packet times out (measured on remote chain)", + "type": "object", + "required": [ + "block" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "both" + ], + "properties": { + "both": { + "type": "object", + "required": [ + "block", + "timestamp_nanos" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "IbcTimeoutBlock": { "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", "type": "object", diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 5a14a72b86..d01aff764b 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -34,7 +34,7 @@ pub use crate::errors::{ #[cfg(feature = "stargate")] pub use crate::ibc::{ IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket, - IbcReceiveResponse, IbcTimeoutBlock, + IbcReceiveResponse, IbcTimeout, IbcTimeoutBlock, }; #[cfg(feature = "iterator")] #[allow(deprecated)] From e83b995442c3e50c107d3ccf340e4a15f0f13bf8 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 13:03:44 +0200 Subject: [PATCH 10/12] Update reflect schema --- contracts/reflect/schema/execute_msg.json | 102 ++++++++++++------ .../schema/response_for__custom_msg.json | 102 ++++++++++++------ 2 files changed, 138 insertions(+), 66 deletions(-) diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json index 9ce71a00b1..bb8c6bf112 100644 --- a/contracts/reflect/schema/execute_msg.json +++ b/contracts/reflect/schema/execute_msg.json @@ -340,6 +340,7 @@ "required": [ "amount", "channel_id", + "timeout", "to_address" ], "properties": { @@ -355,26 +356,14 @@ "description": "exisiting channel to send the tokens over", "type": "string" }, - "timeout_block": { - "description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, "to_address": { "description": "address on the remote chain to receive these tokens", "type": "string" @@ -395,7 +384,8 @@ "type": "object", "required": [ "channel_id", - "data" + "data", + "timeout" ], "properties": { "channel_id": { @@ -404,25 +394,13 @@ "data": { "$ref": "#/definitions/Binary" }, - "timeout_block": { - "description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] - }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 } } } @@ -452,6 +430,64 @@ } ] }, + "IbcTimeout": { + "anyOf": [ + { + "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano", + "type": "object", + "required": [ + "timestamp_nanos" + ], + "properties": { + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "block after which the packet times out (measured on remote chain)", + "type": "object", + "required": [ + "block" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "both" + ], + "properties": { + "both": { + "type": "object", + "required": [ + "block", + "timestamp_nanos" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "IbcTimeoutBlock": { "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", "type": "object", diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json index 9ed98d3f61..0aac6c05a8 100644 --- a/contracts/reflect/schema/response_for__custom_msg.json +++ b/contracts/reflect/schema/response_for__custom_msg.json @@ -328,6 +328,7 @@ "required": [ "amount", "channel_id", + "timeout", "to_address" ], "properties": { @@ -343,26 +344,14 @@ "description": "exisiting channel to send the tokens over", "type": "string" }, - "timeout_block": { - "description": "block after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, "to_address": { "description": "address on the remote chain to receive these tokens", "type": "string" @@ -383,7 +372,8 @@ "type": "object", "required": [ "channel_id", - "data" + "data", + "timeout" ], "properties": { "channel_id": { @@ -392,25 +382,13 @@ "data": { "$ref": "#/definitions/Binary" }, - "timeout_block": { - "description": "block height after which the packet times out. at least one of timeout_block, timeout_timestamp is required", - "anyOf": [ - { - "$ref": "#/definitions/IbcTimeoutBlock" - }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/IbcTimeout" } ] - }, - "timeout_timestamp": { - "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out. See https://golang.org/pkg/time/#Time.UnixNano at least one of timeout_block, timeout_timestamp is required", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 } } } @@ -440,6 +418,64 @@ } ] }, + "IbcTimeout": { + "anyOf": [ + { + "description": "block timestamp (nanoseconds since UNIX epoch) after which the packet times out (measured on the remote chain) See https://golang.org/pkg/time/#Time.UnixNano", + "type": "object", + "required": [ + "timestamp_nanos" + ], + "properties": { + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "block after which the packet times out (measured on remote chain)", + "type": "object", + "required": [ + "block" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "both" + ], + "properties": { + "both": { + "type": "object", + "required": [ + "block", + "timestamp_nanos" + ], + "properties": { + "block": { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + "timestamp_nanos": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "IbcTimeoutBlock": { "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", "type": "object", From 1242d8704a6f2a5322fc3ebab9bf3b887e92c0a6 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 19 Apr 2021 13:43:06 +0200 Subject: [PATCH 11/12] Create Timestamp --- contracts/ibc-reflect-send/src/contract.rs | 10 +++++----- contracts/ibc-reflect-send/src/ibc.rs | 6 +++--- packages/std/src/ibc.rs | 10 ++++------ packages/std/src/lib.rs | 2 ++ packages/std/src/timestamp.rs | 18 ++++++++++++++++++ packages/std/src/types.rs | 11 +++++++++++ 6 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 packages/std/src/timestamp.rs diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index f7d8b80999..4dd9232560 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, - Order, QueryResponse, Response, StdError, StdResult, + attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Order, + QueryResponse, Response, StdError, StdResult, }; use crate::ibc::PACKET_LIFETIME; @@ -91,7 +91,7 @@ pub fn handle_send_msgs( let msg = IbcMsg::SendPacket { channel_id, data: to_binary(&packet)?, - timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), + timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(), }; Ok(Response { @@ -121,7 +121,7 @@ pub fn handle_check_remote_balance( let msg = IbcMsg::SendPacket { channel_id, data: to_binary(&packet)?, - timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), + timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(), }; Ok(Response { @@ -171,7 +171,7 @@ pub fn handle_send_funds( channel_id: transfer_channel_id, to_address: remote_addr, amount, - timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), + timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(), }; Ok(Response { diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index 874a469459..6fa0faafdd 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ attr, entry_point, from_slice, to_binary, DepsMut, Env, IbcAcknowledgement, IbcBasicResponse, - IbcChannel, IbcMsg, IbcOrder, IbcPacket, IbcReceiveResponse, IbcTimeout, StdError, StdResult, + IbcChannel, IbcMsg, IbcOrder, IbcPacket, IbcReceiveResponse, StdError, StdResult, }; use crate::ibc_msg::{ @@ -58,7 +58,7 @@ pub fn ibc_channel_connect( let msg = IbcMsg::SendPacket { channel_id: channel_id.clone(), data: to_binary(&packet)?, - timeout: IbcTimeout::in_secs(&env.block, PACKET_LIFETIME), + timeout: env.block.timestamp().plus_seconds(PACKET_LIFETIME).into(), }; Ok(IbcBasicResponse { @@ -255,7 +255,7 @@ mod tests { mock_dependencies, mock_env, mock_ibc_channel, mock_ibc_packet_ack, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{coin, coins, BankMsg, CosmosMsg, OwnedDeps}; + use cosmwasm_std::{coin, coins, BankMsg, CosmosMsg, IbcTimeout, OwnedDeps}; const CREATOR: &str = "creator"; diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index d1017f5f37..cc3b84004e 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -10,7 +10,7 @@ use std::fmt; use crate::binary::Binary; use crate::coins::Coin; use crate::results::{Attribute, CosmosMsg, Empty, SubMsg}; -use crate::types::BlockInfo; +use crate::timestamp::Timestamp; /// These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts /// (contracts that directly speak the IBC protocol via 6 entry points) @@ -69,11 +69,9 @@ pub enum IbcTimeout { }, } -impl IbcTimeout { - pub fn in_secs(block: &BlockInfo, secs: u64) -> Self { - let secs = block.time + secs; - let nanos = secs * 1_000_000_000 + block.time_nanos; - IbcTimeout::TimestampNanos(nanos) +impl From for IbcTimeout { + fn from(time: Timestamp) -> IbcTimeout { + IbcTimeout::TimestampNanos(time.seconds * 1_000_000_000 + time.nanos) } } diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index d01aff764b..f03c6a5f4e 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -19,6 +19,7 @@ mod results; mod sections; mod serde; mod storage; +mod timestamp; mod traits; mod types; @@ -60,6 +61,7 @@ pub use crate::results::{Context, HandleResponse, InitResponse, MigrateResponse} pub use crate::results::{DistributionMsg, StakingMsg}; pub use crate::serde::{from_binary, from_slice, to_binary, to_vec}; pub use crate::storage::MemoryStorage; +pub use crate::timestamp::Timestamp; pub use crate::traits::{Api, Querier, QuerierResult, QuerierWrapper, Storage}; pub use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo}; diff --git a/packages/std/src/timestamp.rs b/packages/std/src/timestamp.rs new file mode 100644 index 0000000000..2d04191635 --- /dev/null +++ b/packages/std/src/timestamp.rs @@ -0,0 +1,18 @@ +/// A point in time in nanosecond precision. +/// +/// This type cannot represent any time before the UNIX epoch because both fields are unsigned. +pub struct Timestamp { + /// Absolute time in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC). + pub seconds: u64, + /// The fractional part time in nanoseconds since `time` (0 to 999999999). + pub nanos: u64, +} + +impl Timestamp { + pub fn plus_seconds(&self, addition: u64) -> Timestamp { + Timestamp { + seconds: self.seconds + addition, + nanos: self.nanos, + } + } +} diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs index b2e163c04b..fc6ceaa655 100644 --- a/packages/std/src/types.rs +++ b/packages/std/src/types.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::addresses::Addr; use crate::coins::Coin; +use crate::timestamp::Timestamp; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct Env { @@ -63,6 +64,16 @@ pub struct BlockInfo { pub chain_id: String, } +impl BlockInfo { + /// Returns the block creation time as a Timestamp in nanosecond precision + pub fn timestamp(&self) -> Timestamp { + Timestamp { + seconds: self.time, + nanos: self.time_nanos, + } + } +} + /// Additional information from [MsgInstantiateContract] and [MsgExecuteContract], which is passed /// along with the contract execution message into the `instantiate` and `execute` entry points. /// From 61a3749b6978b9f5aba80a7ac863b85e261f9a08 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 19 Apr 2021 17:05:41 +0200 Subject: [PATCH 12/12] Update timeout types in the IBC.md file --- IBC.md | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/IBC.md b/IBC.md index 6873e9184d..a3ee3c33d6 100644 --- a/IBC.md +++ b/IBC.md @@ -32,15 +32,26 @@ pub enum IbcMsg { /// packet data only supports one coin /// https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20 amount: Coin, - /// block after which the packet times out. - /// at least one of timeout_block, timeout_timestamp is required - timeout_block: Option, - /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. - /// See https://golang.org/pkg/time/#Time.UnixNano - /// at least one of timeout_block, timeout_timestamp is required - timeout_timestamp: Option, + /// when packet times out, measured on remote chain + timeout: IbcTimeout, } } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IbcTimeout { + /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out + /// (measured on the remote chain) + /// See https://golang.org/pkg/time/#Time.UnixNano + TimestampNanos(u64), + /// block after which the packet times out (measured on remote chain) + Block(IbcTimeoutBlock), + Both { + timestamp_nanos: u64, + block: IbcTimeoutBlock, + }, +} + ``` Note the `to_address` is likely not a valid `Addr`, as it uses the bech32 prefix @@ -226,13 +237,8 @@ pub enum IbcMsg { SendPacket { channel_id: String, data: Binary, - /// block height after which the packet times out. - /// at least one of timeout_block, timeout_timestamp is required - timeout_block: Option, - /// block timestamp (nanoseconds since UNIX epoch) after which the packet times out. - /// See https://golang.org/pkg/time/#Time.UnixNano - /// at least one of timeout_block, timeout_timestamp is required - timeout_timestamp: Option, + /// when packet times out, measured on remote chain + timeout: IbcTimeout, }, } ```