Changelog
- 09-07-2020: Initial Draft
- 11-08-2020: Implementation changes
Status
Accepted, ImplementedContext
The specification for IBC cross-chain fungible token transfers (ICS20), needs to be aware of the origin of any token denomination in order to relay aPacket which contains the sender
and recipient addresses in the
FungibleTokenPacketData.
The Packet relay sending works based in 2 cases (per
specification and Colin Axnér’s description):
- Sender chain is acting as the source zone. The coins are transferred to an escrow address (i.e locked) on the sender chain and then transferred to the receiving chain through IBC TAO logic. It is expected that the receiving chain will mint vouchers to the receiving address.
- Sender chain is acting as the sink zone. The coins (vouchers) are burned on the sender chain and then transferred to the receiving chain through IBC TAO logic. It is expected that the receiving chain, which had previously sent the original denomination, will unescrow the fungible token and send it to the receiving address.
Example
Assume the following channel connections exist and that all channels use the port IDtransfer:
- chain
Ahas channels with chainBand chainCwith the IDschannelToBandchannelToC, respectively - chain
Bhas channels with chainAand chainCwith the IDschannelToAandchannelToC, respectively - chain
Chas channels with chainAand chainBwith the IDschannelToAandchannelToB, respectively
A -> B -> C -> A -> C. In particular:
A -> B: sender chain is source zone.Asends packet withdenom(escrowed onA),Breceivesdenomand mints and sends vouchertransfer/channelToA/denomto recipient.B -> C: sender chain is source zone.Bsends packet withtransfer/channelToA/denom(escrowed onB),Creceivestransfer/channelToA/denomand mints and sends vouchertransfer/channelToB/transfer/channelToA/denomto recipient.C -> A: sender chain is source zone.Csends packet withtransfer/channelToB/transfer/channelToA/denom(escrowed onC),Areceivestransfer/channelToB/transfer/channelToA/denomand mints and sends vouchertransfer/channelToC/transfer/channelToB/transfer/channelToA/denomto recipient.A -> C: sender chain is sink zone.Asends packet withtransfer/channelToC/transfer/channelToB/transfer/channelToA/denom(burned onA),Creceivestransfer/channelToC/transfer/channelToB/transfer/channelToA/denom, and unescrows and sendstransfer/channelToB/transfer/channelToA/denomto recipient.
C of transfer/channelToB/transfer/channelToA/denom, where transfer/channelToB/transfer/channelToA is the trace information.
In this context, upon a receive of a cross-chain fungible token transfer, if the sender chain is the source of the token, the protocol prefixes the denomination with the port and channel identifiers in the following format:
100 uatom from port HubPort and channel HubChannel on the Hub to
Ethermint’s port EthermintPort and channel EthermintChannel results in 100 EthermintPort/EthermintChannel/uatom, where EthermintPort/EthermintChannel/uatom is the new
denomination on the receiving chain.
In the case those tokens are transferred back to the Hub (i.e the source chain), the prefix is
trimmed and the token denomination updated to the original one.
Problem
The problem of adding additional information to the coin denomination is twofold:- The ever increasing length if tokens are transferred to zones other than the source:
n times via IBC to a sink chain, the token denom will contain n pairs
of prefixes, as shown on the format example above. This poses a problem because, while port and
channel identifiers have a maximum length of 64 each, the SDK Coin type only accepts denoms up to
64 characters. Thus, a single cross-chain token, which again, is composed by the port and channels
identifiers plus the base denomination, can exceed the length validation for the SDK Coins.
This can result in undesired behaviours such as tokens not being able to be transferred to multiple
sink chains if the denomination exceeds the length or unexpected panics due to denomination
validation failing on the receiving chain.
- The existence of special characters and uppercase letters on the denomination:
Coin is initialized through the constructor function NewCoin, a validation
of a coin’s denom is performed according to a
Regex,
where only lowercase alphanumeric characters are accepted. While this is desirable for native denominations
to keep a clean UX, it presents a challenge for IBC as ports and channels might be randomly
generated with special and uppercase characters as per the ICS 024 - Host
Requirements
specification.
Decision
The issues outlined above, are applicable only to SDK-based chains, and thus the proposed solution are do not require specification changes that would result in modification to other implementations of the ICS20 spec. Instead of adding the identifiers on the coin denomination directly, the proposed solution hashes the denomination prefix in order to get a consistent length for all the cross-chain fungible tokens. This will be used for internal storage only, and when transferred via IBC to a different chain, the denomination specified on the packed data will be the full prefix path of the identifiers needed to trace the token back to the originating chain, as specified on ICS20. The new proposed format will be the following:DenomTrace:
IBCDenom function constructs the Coin denomination used when creating the ICS20 fungible token packet data:
x/ibc-transfer Changes
In order to retrieve the trace information from an IBC denomination, a lookup table needs to be
added to the ibc-transfer module. These values need to also be persisted between upgrades, meaning
that a new []DenomTrace GenesisState field state needs to be added to the module:
MsgTransfer will validate that the Coin denomination from the Token field contains a valid
hash, if the trace info is provided, or that the base denominations matches:
- Receiver is source chain: The receiver created the token and must have the trace lookup already stored (if necessary ie native token case wouldn’t need a lookup).
- Receiver is not source chain: Store the received info. For example, during step 1, when chain
Breceivestransfer/channelToA/denom.
FungibleTokenPacketData will remain the same, i.e with the prefixed full denomination, since the receiving chain may not be an SDK-based chain.
Coin Changes
The coin denomination validation will need to be updated to reflect these changes. In particular, the denomination validation function will now:- Accept slash separators (
"/") and uppercase characters (due to theHexBytesformat) - Bump the maximum character length to 128, as the hex representation used by Tendermint’s
HexBytestype contains 64 characters.
Positive
- Clearer separation of the source tracing behaviour of the token (transfer prefix) from the original
Coindenomination - Consistent validation of
Coinfields (i.e no special characters, fixed max length) - Cleaner
Coinand standard denominations for IBC - No additional fields to SDK
Coin
Negative
- Store each set of tracing denomination identifiers on the
ibc-transfermodule store - Clients will have to fetch the base denomination every time they receive a new relayed fungible token over IBC. This can be mitigated using a map/cache for already seen hashes on the client side. Other forms of mitigation, would be opening a websocket connection subscribe to incoming events.
Neutral
- Slight difference with the ICS20 spec
- Additional validation logic for IBC coins on the
ibc-transfermodule - Additional genesis fields
- Slightly increases the gas usage on cross-chain transfers due to access to the store. This should be inter-block cached if transfers are frequent.