IBC integration
IBC transaction
An IBC transaction tx_ibc.wasm
is provided. We have to set an IBC message to the transaction data corresponding to execute an IBC operation.
The transaction decodes the data to an IBC message and handles IBC-related data, e.g. it makes a new connection ID and writes a new connection end for MsgConnectionOpenTry
. The operations are implemented in IbcActions
. The transaction doesn't check the validity for the state changes. IBC validity predicate and IBC token validity predicate are in charge of the validity.
IBC validity predicate
IBC validity predicate checks if an IBC transaction satisfies IBC protocol. When an IBC transaction is executed, i.e. a transaction changes the state of the key that contains InternalAddress::Ibc
, IBC validity predicate (one of the native validity predicates) is executed. For example, if an IBC connection end is created in the transaction, IBC validity predicate validates the creation. If the creation with MsgConnectionOpenTry
is invalid, e.g. the counterpart connection end doesn't exist, the validity predicate makes the transaction fail.
Fungible Token Transfer
The transfer of fungible tokens over an IBC channel on separate chains is defined in ICS20.
In Namada, the sending tokens is triggered by a transaction having MsgTransfer as transaction data. A packet including FungibleTokenPacketData
is made from the message in the transaction execution.
Namada chain receives the tokens by a transaction having MsgRecvPacket which has the packet including FungibleTokenPacketData
.
The sending and receiving tokens in a transaction are validated by not only IBC validity predicate but also IBC token validity predicate. IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. an unexpected amount is minted, the validity predicate makes the transaction fail.
A transaction escrowing/unescrowing a token changes the escrow account's balance of the token. The key is {token_addr}/ibc/{port_id}/{channel_id}/balance/IbcEscrow
. A transaction burning a token changes the burn account's balance of the token. The key is {token_addr}/ibc/{port_id}/{channel_id}/balance/IbcBurn
. A transaction minting a token changes the mint account's balance of the token. The key is {token_addr}/ibc/{port_id}/{channel_id}/balance/IbcMint
. The key including IbcBurn
or IbcMint
have the balance temporarily for validity predicates. It isn't committed to a block. IbcEscrow
, IbcBurn
, and IbcMint
are addresses of InternalAddress
and actually they are encoded in the storage key. When these addresses are included in the changed keys after transaction execution, IBC token validity predicate is triggered.
The receiver's account is {token_addr}/ibc/{ibc_token_hash}/balance/{receiver_addr}
. {ibc_token_hash}
is a hash calculated with the denomination prefixed with the port ID and channel ID. It is NOT the same as the normal account {token_addr}/balance/{receiver_addr}
. That's because it should be origin-specific for transferring back to the source chain. We can transfer back the received token by setting ibc/{ibc_token_hash}
or {port_id}/{channel_id}/{token_addr}
as denom
in MsgTransfer
.
For example, we transfer a token #my_token
from a user #user_a
on Chain A to a user #user_b
on Chain B, then transfer back the token from #user_b
to #user_a
. The port ID and channel ID on Chain A for Chain B are transfer
and channel_42
, those on Chain B for Chain A are transfer
and channel_24
. The denomination in the FungibleTokenTransferData
at the first transfer should be #my_token
.
- User A makes
MsgTransfer
as a transaction data and submits a transaction from Chain A
#![allow(unused)] fn main() { let token = Some(Coin { denom, // #my_token amount: "100000".to_string(), }); let msg = MsgTransfer { source_port, // transfer source_channel, // channel_42 token, sender, // #user_a receiver, // #user_b timeout_height: Height::new(0, 1000), timeout_timestamp: (Timestamp::now() + Duration::new(100, 0)).unwrap(), }; }
- On Chain A, the specified amount of the token is transferred from the sender's account
#my_token/balance/#user_a
to the escrow account#my_token/ibc/transfer/channel_42/balance/IbcEscrow
- On Chain B, the amount of the token is transferred from
#my_token/ibc/transfer/channel_24/balance/IbcMint
to#my_token/ibc/{hash}/balance/#user_b
- The
{hash}
is calculated from a stringtransfer/channel_24/#my_token
with SHA256 - The
{hash}
is a fixed length because of hashing even if the original denomination becomes too long with many prefixes after transferring through many chains
- The
- To transfer back, User B makes
MsgTransfer
and submits a transaction from Chain B
#![allow(unused)] fn main() { let token = Some(Coin { denom, // ibc/{hash} or transfer/channel_24/#my_token amount: "100000".to_string(), }); let msg = MsgTransfer { source_port, // transfer source_channel, // channel_24 token, sender, // #user_b receiver, // #user_a timeout_height: Height::new(0, 1000), timeout_timestamp: (Timestamp::now() + Duration::new(100, 0)).unwrap(), }; }
- On Chain B, the amount of the token is transferred from
#my_token/ibc/{hash}/balance/#user_b
to#my_token/ibc/transfer/channel_24/IbcBurn
- On Chain A, the amount of the token is transferred from
#my_token/ibc/transfer/channel_42/balance/IbcEscrow
to#my_token/balance/#user_a
IBC message
IBC messages are defined in ibc-rs
. The message should be encoded with Protobuf (NOT with Borsh) as the following code to set it as a transaction data.
#![allow(unused)] fn main() { use ibc::tx_msg::Msg; pub fn make_ibc_data(message: impl Msg) -> Vec<u8> { let msg = message.to_any(); let mut tx_data = vec![]; prost::Message::encode(&msg, &mut tx_data).expect("encoding IBC message shouldn't fail"); tx_data } }
-
Client
- MsgCreateAnyClient
- MsgSubmitAnyMisbehaviour (NOT supported yet)
- MsgUpdateAnyClient
- MsgUpgradeAnyClient
-
Connection
-
Channel
-
ICS20 FungibleTokenTransfer