ics | title | stage | category | author | created | modified |
---|---|---|---|---|---|---|
999 |
One Channel |
draft |
IBC/APP |
Larry Engineer <[email protected]> |
2023-03-02 |
2023-03-02 |
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.
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.
- 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.
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.
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
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 itssend_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 theone-transfer
contract to mint newibc/*
tokens or release tokens from escrow;register_account
: instantiates a newone-host
contract;execute
: invokes theexecute
API at the approriate host contract.
Note that the execution of actions is ordered and atomic, solving the two major problems mentioned earlier.
We can add interchain query and relayer fee mechanisms to this as well.
Mar 2, 2023 - First draft
TBD
I think that this is likely portable to go (significant rework needed tho) and useful in that context also. I like it tons.