# IBC specification This documents how CosmWasm contracts are expected to interact with IBC. ## General Concepts **IBC Enabled** - when instantiating a contract, we detect if it supports IBC messages. We require "feature flags" in the contract/vm handshake to ensure compatibility for features like staking or chain-specific extensions. IBC functionality will require another "feature flag", and the list of "enabled features" can be returned to the `x/wasm` module to control conditional IBC behavior. If this feature is enabled, it is considered "IBC Enabled", and that info will be stored in the ContractInfo. (For mock, we assume all contracts are IBC enabled) Also, please read the [IBC Docs](https://docs.cosmos.network/master/ibc/overview.html) for detailed descriptions of the terms *Port*, *Client*, *Connection*, and *Channel* ## Overview We use "One Port per Contract", which is the most straight-forward mapping, treating each contract like a module. It does lead to very long portIDs however. Pay special attention to both the Channel establishment (which should be compatible with standard ICS20 modules without changes on their part), as well as how contracts can properly identify their counterparty. (We considered on port for the `x/wasm` module and multiplexing on it, but [dismissed that idea](#rejected-ideas)) * Upon `Instantiate`, if a contract is *IBC Enabled*, we dynamically bind a port for this contract. The port name is `wasm-`, eg. `wasm-cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29` * If a *Channel* is being established with a registered `wasm-xyz` port, the `x/wasm.Keeper` will handle this and call into the appropriate contract to determine supported protocol versions during the [`ChanOpenTry` and `ChanOpenAck` phases](https://docs.cosmos.network/master/ibc/overview.html#channels). (See [Channel Handshake Version Negotiation](https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation)) * Both the *Port* and the *Channel* are fully owned by one contract. * `x/wasm` will allow both *ORDERED* and *UNORDERED* channels and pass that mode down to the contract in `OnChanOpenTry`, so the contract can decide if it accepts the mode. We will recommend the contract developers stick with *ORDERED* channels for custom protocols unless they can reason about async packet timing. * When sending a packet, the CosmWasm contract must specify the local *ChannelID*. As there is a unique *PortID* per contract, that is filled in by `x/wasm` to produce the globally unique `(PortID, ChannelID)` * When receiving a Packet (or Ack or Timeout), the contracts receives the *ChannelID* it came from, as well as the packet that was sent by the counterparty. * When receiving an Ack or Timeout packet, the contract also receives the original packet that it sent earlier. * We do not support multihop packets in this model (they are rejected by `x/wasm`). They are currently not fully specified nor implemented in IBC 1.0, so let us simplify our model until this is well established ## Workflow Establishing *Clients* and *Connections* is out of the scope of this module and must be created by the same means as for `ibc-transfer` (via the cli or otherwise). `x/wasm` will bind a unique *Port* for each "IBC Enabled" contract. For mocks, all the Packet Handling and Channel Lifecycle Hooks are routed to some Golang stub handler, but containing the contract address, so we can perform contract-specific actions for each packet. ### Messages An "IBC Enabled" contract may dispatch the following messages not available to other contracts: * `IBCSendMsg` - this sends an IBC packet over an established channel. * `IBCCloseChannel` - given an existing channelID bound to this contract's Port, initiate the closing sequence and reject all pending packets. They are returned from `handle` just like any other `CosmosMsg` (For mocks, we will trigger this externally, later only valid contract addresses should be able to do so). ### Packet Handling An "IBC Enabled" contract must support the following callbacks from the runtime (we will likely multiplex many over one wasm export, as we do with handle, but these are the different calls we must support): * `IBCRecvPacket` - when another chain sends a packet destined to an IBCEnabled contract, this will be routed to the proper contract and call a function exposed for this purpose. * `IBCPacketAck` - The original sender of `IBCSendMsg` will get this callback eventually if the message was processed on the other chain (this may be either a success or an error, but comes from the app-level protocol, not the IBC protocol). * `IBCPacketTimeout` - The original sender of `IBCSendMsg` will get this callback eventually if the message failed to be processed on the other chain (for timeout, closed channel, or other IBC-level failure) Note: We may add some helpers inside the contract to map `IBCPacketAck` / `IBCPacketTimeout` to `IBCPacketSucceeded` / `IBCPacketFailed` assuming they use the standard envelope. However, we decided not to enforce this on the Go-level, to allow contracts to communicate using protocols that do not use this envelope. ### Channel Lifecycle Hooks If you look at the [4 step process](https://docs.cosmos.network/master/ibc/overview.html#channels) for channel handshakes, we simplify this from the view of the contract: 1. The main path to initiate a channel from a contract to an external port is via an external client executing `simd tx ibc channel open-init` or such. This allows the initiating party to select which version/protocol they want to connect with. There must be a callback for the initiating contract to verify if the version and/or ordering are valid. **Question**: can we reuse the same check-logic as for step 2 2. The counterparty has a chance for version negotiation in `OnChanOpenTry`, where the contract can apply custom logic. It provides the protocol versions that the initiating party expects, and the contract can reject the connection or accept it and return the protocol version it will communicate with. Implementing this (contract selects a version, not just the relayer) requires that we support [ADR 025](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-025-ibc-passive-channels.md). TODO: check with Agoric and cwgoes to upstream any Cosmos SDK changes so this can work out of the box. 3. `OnChanOpened` is called on the contract for both `OnChanOpenAck` and `OnChanOpenConfirm` containing the final version string (counterparty version). This gives a chance to abort the process if we realize this doesn't work. Or save the info (we may need to define this channel uses an older version of the protocol for example). 4. `OnChanClosed` is called on both sides if the channel is closed for any reason, allowing them to perform any cleanup. This will be followed by `IBCPacketTimeout` callbacks for all the in-progress packets that were not processed before it closed, as well as all pending acks (pending the relayer to provide that). We require the following callbacks on the contract * `OnChanNegotiate` - called on the initiating side on `OnChanOpenInit` and on receiving end for `OnChanOpenTry`. @alpe: does it make sense to use the same shape for both of these? Rather than `CounterPartyVersion`, they would have `ProposedVersion`, but otherwise the same logic. * `OnChanOpened` - called on both sides after the initial 2 steps have passed to confirm the version used * `OnChanClosed` - this is called when an existing channel is closed for any reason ### Queries We may want to expose some basic queries of IBC State to the contract. We should check if this is useful to the contract and if it opens up any possible DoS: * `GetPortID` - return PortID given a contract address * `ListChannels` - return a list of all (portID, channelID) pairs that are bound to a given port. * `ListPendingPackets` - given a (portID, channelID) identifier, return all packets in that channel, that have been sent by this chain, but for which no acknowledgement or timeout has yet been received * `ListPendingAcknowledgements` - given a (portID, channelID) identifier, return all packets in that channel, that have been received and processed by this chain, but for which we have no proof the acknowledgement has been relayed * `GetCounterparty` - given a local (portID, channelID) identifier, it will look up what (portID, channelID) are bound to the remote end of the channel. ## Contract Details Here we map out the workflow with the exact arguments passed with those calls from the Go side (which we will use with our mock), and then a proposal for multiplexing this over fewer wasm exports (define some rust types) Messages: ```go package messages type IBCSendMsg struct { // This is our contract-local ID ChannelID string Msg []byte // optional fields (or do we need exactly/at least one of these?) TimeoutHeight uint64 TimeoutTimestamp uint64 } type IBCCloseChannel struct { ChannelID string } // "Versions must be strings but and implement any versioning structure..." // https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation // Use the same approach here type Version string ``` Packet callbacks: ```go package packets // for reference: this is more like what we pass to wasmvm // func (c *mockContract) OnReceive(params cosmwasm2.Env, msg []byte, store prefix.Store, api cosmwasm.GoAPI, // querier keeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm2.OnReceiveIBCResponse, uint64, error) {} // below is how we want to expose it in x/wasm: // IBCRecvPacket is called when we receive a packet sent from the other end of a channel // The response bytes listed here will be returned to the caller as "result" in IBCPacketAck // What do we do with error? // // If we were to assume/enforce an envelope, then we could wrap response/error into the acknowledge packet, // but we delegated that encoding to the contract func IBCRecvPacket(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, msg []byte) (response []byte, err error) {} // how to handle error here? if we push it up the ibc stack and fail the transaction (normal handling), // the packet may be posted again and again. just log and ignore failures here? what does a failure even mean? // only realistic one I can imagine is OutOfGas (panic) to retry with higher gas limit. // // if there any point in returning a response, what does it mean? func IBCPacketAck(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, originalMsg []byte, result []byte) error {} // same question as for IBCPacketAck func IBCPacketDropped(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, env IBCPacketInfo, originalMsg []byte, errorMsg string) error {} // do we need/want all this info? type IBCPacketInfo struct { // local id for the Channel packet was sent/received on ChannelID string // sequence for the packet (will already be enforced in order if ORDERED channel) Sequence uint64 // Note: Timeout if guaranteed ONLY to be exceeded iff we are processing IBCPacketDropped // otherwise, this is just interesting metadata // block height after which the packet times out TimeoutHeight uint64 // block timestamp (in nanoseconds) after which the packet times out TimeoutTimestamp uint64 } ``` Channel Lifecycle: ```go package lifecycle // if this returns error, we reject the channel opening // otherwise response has the versions we accept // // It is provided the full ChannelInfo being proposed to do any needed checks func OnChanNegotiate(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelInfo) (version Version, err error) {} // This is called with the full ChannelInfo once the other side has agreed. // An error here will still abort the handshake process. (so you can double check the order/version here) // // The main purpose is to allow the contract to set up any internal state knowing the channel was established, // and keep a registry with that ChannelID func OnChanOpened(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelInfo) error {} // This is called when the channel is closed for any reason // TODO: any meaning to return an error here? we cannot abort closing a channel func OnChanClosed(ctx sdk.Context, k *wasm.Keeper, contractAddress sdk.AccAddress, request ChannelClosedInfo) error {} type ChannelInfo struct { // key info to enforce (error if not what is expected) Order channeltypes.Order // The proposed version. This may come from the relayer, from the initiating contract, or the responding contract. // In any case, this is the currently agreed upon version to use and if there is disagreement, the contract should // propose another one (if possible in return value), or return an error ProposedVersion Version // local id for the Channel that is being initiated ChannelID string // these two are taken from channeltypes.Counterparty RemotePortID string RemoteChannelID string } type ChannelClosedInfo struct { // local id for the Channel that is being shut down ChannelID string } ``` Queries: These are callbacks that the contract can make, calling into a QueryPlugin. The general type definition for a `QueryPlugin` is `func(ctx sdk.Context, request *wasmTypes.IBCQuery) ([]byte, error)`. All other info (like the contract address we are querying for) must be passed in from the contract (which knows it's own address). Here we just defined the request and response types (which will be serialized into `[]byte`) ```go package queries type QueryPort struct { ContractAddress string } type QueryPortResponse struct { PortID string } type QueryChannels struct { // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping PortID string ContractAddress string } type QueryChannelsResponse struct { ChannelIDs []ChannelMetadata } type ChannelMetadata struct { // Local portID, channelID is our unique identifier PortID string ChannelID string RemotePortID string Order channeltypes.Order Verson Version } type QueryPendingPackets struct { // Always required ChannelID string // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping PortID string ContractAddress string } type QueryPendingPacketsResponse struct { Packets []PacketMetadata } type PacketMetadata struct { // The original (serialized) message we sent Msg []byte Sequence uint64 // block height after which the packet times out TimeoutHeight uint64 // block timestamp (in nanoseconds) after which the packet times out TimeoutTimestamp uint64 } type QueryPendingAcknowlegdements struct { // Always required ChannelID string // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping PortID string ContractAddress string } type QueryPendingPacketsResponse struct { // Do we need another struct for the metadata? Packets []PacketMetadata } type QueryCounterparty struct { // Always required ChannelID string // exactly one of these must be set. ContractAddress is a shortcut to save the Contract->PortID mapping PortID string ContractAddress string } // lists (port, channel) on the counterparty for our local channel type QueryCounterpartyResponse struct { PortID string ChannelID string } ``` ### Contract (Wasm) entrypoints **TODO** ## Future Ideas Here are some ideas we may add in the future ### Dynamic Ports and Channels * multiple ports per contract * elastic ports that can be assigned to different contracts * transfer of channels to another contract This is inspired by the Agoric design, but also adds considerable complexity to both the `x/wasm` implementation as well as the correctness reasoning of any given contract. This will not be available in the first version of our "IBC Enabled contracts", but we can consider it for later, if there are concrete user cases that would significantly benefit from this added complexity. ### Add multihop support Once the ICS and IBC specs fully establish how multihop packets work, we should add support for that. Both on setting up the routes with OpenChannel, as well as acting as an intermediate relayer (if that is possible) ## Rejected Ideas ### One Port per Module We decided on "one port per contract", especially after the IBC team raised the max length on port names to allow `wasm-` to be a valid port. Here are the arguments for "one port for x/wasm" vs "one port per contract". Here was an alternate proposal: In this approach, the `x/wasm` module just binds one port to handle all modules. This can be well defined name like `wasm`. Since we always have `(ChannelID, PortID)` for routing messages, we can reuse one port for all contracts as long as we have a clear way to map the `ChannelID` to a specific contract when it is being established. * On genesis we bind the port `wasm` for all communication with the `x/wasm` module. * The *Port* is fully owned by `x/wasm` * Each *Channel* is fully owned by one contract. * `x/wasm` only accepts *ORDERED Channels* for simplicity of contract correctness. To clarify: * When a *Channel* is being established with port `wasm`, the `x/wasm.Keeper` must be able to identify for which contract this is destined. **how to do so**?? * One idea: the channel name must be the contract address. This means (`wasm`, `cosmos13d...`) will map to the given contract in the wasm module. The problem with this is that if two contracts from chainA want to connect to the same contracts on chainB, they will want to claim the same *ChannelID* and *PortID*. Not sure how to differentiate multiple parties in this way. * Other ideas: have a special field we send on `OnChanOpenInit` that specifies the destination contract, and allow any *ChannelID*. However, looking at [`OnChanOpenInit` function signature](https://docs.cosmos.network/master/ibc/custom.html#implement-ibcmodule-interface-and-callbacks), I don't see a place to put this extra info, without abusing the version field, which is a [specified field](https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation): ``` Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. ... Valid version selection includes selecting a compatible version identifier with a subset of features supported by your application for that version. ... ICS20 currently implements basic string matching with a single supported version. ```