Skip to content

Instantly share code, notes, and snippets.

@larry0x
Last active July 13, 2023 18:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save larry0x/f12d57d693f15bfed18432d930fda080 to your computer and use it in GitHub Desktop.
Save larry0x/f12d57d693f15bfed18432d930fda080 to your computer and use it in GitHub Desktop.
ics title stage category author created modified
999
One Channel
draft
IBC/APP
Larry Engineer <gm@larry.engineer>
2023-03-02
2023-03-02

Synopsis

One channel to rule them all...

This document specifies packet data structure, state machine handling details, and encoding details for ICS-999 One Channel, an IBC protocol designed specifically for cw-sdk chains, which has the capabilities of both fungible token transfer and message execution.

Motivation

A big problem in developing an interchain app is the fact that fungible token transfer (ICS-20) and message execution (ICS-27) are handled by two separate channels. This leads to two issues:

  • It is impossible to enforce order between a token transfer and a message execution.

    For example, I want to send some OSMO tokens to my interchain account (ICA) on Osmosis, then stake them. To do this, I send an ICS-20 packet containing the token, and an ICS-27 packet containing a single MsgDelegate.

    For this to work, the ICS-20 packet must be delivered prior to the ICS-27 packet. However, it's impossible to guarantee this, as the two packets are on different channels.

    In practice, I have to wait for the acknowledgement of the ICS-20 packet, only by when I know for sure it has been delivered, before sending the ICS-27 packet.

  • It is impossible to send some tokens, then execute a message using those tokens, in an atomic manner.

    "Atomic" here means that if the message execution has failed, the token transfer will also fail and be reverted.

    This is currently not possible, as ICS-20 and 27 are on different channels, an ICS-27 execution failing does not have any effect on handling of ICS-20 packets.

    As a developer, I will have to program my app to handle the case where the ICS-20 packet succeeds but the ICS-27 packet fails, which is often not a trivial task.

On a separate matter, there is the issue that ICS-20 only supports one coin per packet. This is more of an inconvenience rather than a serious problem, but still it'd be better if it supports multiple coins in one packet.

Desired properties

  • Support both fungible token transfer, and message execution via interchain accounts.
  • For fungible token transfer, the packet should support multiple coins.
  • Developer experience should tailored for cw-sdk and CosmWasm developers.

Compatibility with existing ICS-20 and 27 channels is not being considered, as their designs have deviated too much that it doesn't seem feasible to make them compatible.

Technical specification

Channel

ICS-999 will use port ID one.

The One Channel is an unordered channel. If a sender wishes to enforce order between multiple token transfers and message executions, they should put those actions in an ordered array in a single packet (see below).

Ideally, there is only one ICS-999 channel for each connection.

Data structures

The ICS-999 One Channel packet data type is as follows, defined in Rust:

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, ReplyOn, WasmMsg};

#[cw_serde]
struct Packet {
    sender: String,
    actions: Vec<Action>,
    reply_on: ReplyOn,
}

#[cw_serde]
enum Action {
    /// Send one or more tokens to a recipient.
    Transfer {
        recipient: String,
        amount: Vec<Coin>,
    },

    /// Register an interchain account.
    RegisterAccount,

    /// Instructs the interchain account to execute a wasm message.
    Execute(WasmMsg),
}

The packet should be serialized as JSON (using serde-json-wasm library). If used in Protobuf Any type, the type URL is:

/cosmwasm.sdk.OnePacket

Packet handling

The ICS-999 protocol consists of three components:

Component Description
one handles packet commitment and execution
one-transfer handles fungible token transfer
one-host serves as interchain account host

For cw-sdk chains, each components is to be implemented as a CosmWasm contract; for cosmos-sdk chains, a "wrapper" Go module can be created, which mediates messages between and ibc module and the three contracts.

The one contract must implement the channel handshake methods, as well as the following API:

#[cw_serde]
enum ExecuteMsg {
    /// Invoked by any user who wishes to send a packet.
    SendPacket {
        channel_id: String,
        actions: Vec<Action>,
    },
}

#[cw_serde]
enum SudoMsg {
    /// Invoked by the IBC core layer when a packet is received.
    ReceivePacket {
        channel_id: String,
        packet: Packet,
    },
}

The one-transfer contract must implement the following API:

#[cw_serde]
enum ExecuteMsg {
    /// Invoked by the `one` contract if a user sends a packet that contains a
    /// `Transfer` action.
    SendTransfer {
        channel_id: String,
        amount: Vec<Coin>,
    },

    /// Invoked by the `one` contract if a packet is received that contains a
    /// `Transfer` action.
    ReceiveTransfer {
        channel_id: String,
        amount: Vec<Coin>,
    },
}

The one-host contract must implement the following instantiate API:

#[cw_serde]
enum ExecuteMsg {
    /// Invoked the the `one` contract if a packet is received that contains an
    /// `Execute` action.
    Execute(WasmMsg),
}

To send a packet, the sender invokes the send_packet method. The contract must assert that the sender has attached the correct amount of coins along with the packet. If this is the case, the contract:

  • composes the packet and instructs the IBC core layer to commit it;
  • sends tokens to the one-transfer contract to be escrowed or burned, by invoking its send_transfer method.

One the receiver chain, the IBC core layer delivers the packet to the one contract by invoking its receive_packet sudo API. The contract then handles the actions in order. Specifically:

  • transfer: invokes the one-transfer contract to mint new ibc/* tokens or release tokens from escrow;
  • register_account: instantiates a new one-host contract;
  • execute: invokes the execute API at the approriate host contract.

Note that the execution of actions is ordered and atomic, solving the two major problems mentioned earlier.

Future plans

We can add interchain query and relayer fee mechanisms to this as well.

History

Mar 2, 2023 - First draft

Copyright

TBD

@faddat
Copy link

faddat commented Mar 2, 2023

I think that this is likely portable to go (significant rework needed tho) and useful in that context also. I like it tons.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment