Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify ics20-2 #577

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 138 additions & 2 deletions spec/app/ics-020-fungible-token-transfer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The IBC handler interface & IBC routing module interface are as defined in [ICS
- Permissionless token transfers, no need to whitelist connections, modules, or denominations.
- Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones).
- Fault containment: prevents Byzantine-inflation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behaviour (though any users who sent tokens to chain `B` may be at risk).
- Opt-in, decentralized, pseudonymous relayer incentivization ([See ICS20-2](#Extension-ICS20-2))

## Technical Specification

Expand Down Expand Up @@ -330,13 +331,146 @@ In order to track all of the denominations moving around the network of chains i
- Each chain, locally, could elect to keep a lookup table to use short, user-friendly local denominations in state which are translated to and from the longer denominations when sending and receiving packets.
- Additional restrictions may be imposed on which other machines may be connected to & which channels may be established.

## Extension: ICS20-2

The above specifications defines `ics20-1`, which is the first version of the specification. Here we define
ICS20-2 which

### Goals

- Add support for opt-in, decentralized, pseudonymous relayer incentivization
- Backwards compatibility with ics20-1
- Minimal extra logic needed to maintain backwards compatibility
- Fault-resilient: version implementation errors should not break invariants

### Data Structures

In order to help with backwards compatibility, we define a packet structure for ICS20-2, which is a superset of ICS20-1, such that any valid packet for one protocol, is also a valid packet for the other protocol. The only issue is that the `fee` info from a ICS20-2 packet will be ignored by a ICS20-1 handler.

```typescript
interface FungibleTokenPacketData {
denomination: string
amount: uint256
sender: string
receiver: string
fee: Maybe<uint256>
}
```

Fee is defined here as some number of tokens to be sent to the address who submitted the IbcReceivePacket on the destination chain. The information of `signer` is available when submitting the packet, but discarded in the application-specific handlers for `ibc-go`. We can simply expose that information to the application to allow it to optionally take action based on who submitted the packet. Note that this means *anyone* can submit a valid packet and we do not hard code an allowed relayer nor force the token sender to select a relayer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to consider a relayer address to be optionally provided by the message sender to support permissioned relaying? If field is left empty (default) anyone can relay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us discuss this. I believe there was a great concern that permissioned relaying would involve money transfer laws and require heavy regulation. We should wait until the lawyers come back with their analysis of the law, but I would add a comment on this in any case.

In order to maintain maximum compatibility with `ics20-1`, we will define `amount` and `fee` in such a way that a valid `ics20-2` sender and a valid `ics20-1` recipient will not create or destroy any tokens (just ignore the fee field when set). That means:

- `amount` is the total amount that is escrowed by the sending chain.
- `amount - (fee || 0)` is the total amount to be sent to the `receiver` on the receiving chain
- `fee || 0` is the total amount to be sent to the relayer account (`signer`)
Comment on lines +362 to +366
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think that we should allow fee to be payed in a different denomination. Since ICS-20 currently only supports a single coin amount, we would need to change the logic here such that amount+fee gets escrowed on sender chain to support this.

Even with the existence of AMM, I don't expect relayers to be perfectly agnostic as to what denomination they get paid in. I still expect that they will want to be paid in a very liquid, high market-cap token.

The proposal as is would work fine for ICS-20 transfer packets sending well-known desirable tokens. However, packets sending low-liquidity, low-market-cap tokens will be greatly disadvantaged. They will either have to pay an exorbitant fee or their packets may not get relayed at all.

Suppose I create adityaCoin and even set up a small, very lightly-used liquidity pool for it to trade against ATOM. But no one cares about having adityaCoin except you. I should be able to send you adityaCoin over IBC and incentivize a relayer to relay it using ATOM as a fee. Even with the AMM, a relayer will not accept adityaCoin. I believe the AMM will cause relayers to accept fees in any well-traded coin, but not from arbitrary denominations.

From a user perspective, I want my packets relayed and treated the same regardless of what denomination my amount is in and am willing to pay my fee in ATOM or ETH to get my adityaCoin relayed.
From a relayer perspective, I want to be paid in my denomination(s) of choice OR in a denomination that is easily swappable for my preferred denomination (little to no slippage).

Thus, I think having a different denom fee is a must have even for MVP and especially because it will be used in a DEX where denominations with wildly different liquidities/desirability/market-cap are going to be sent around and they all need to be relayed just the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point


Defining `amount + fee` to be escrowed on the sending chain would potentially cause lost tokens if `fee` were set in the packet sender, but the recipient ignored the field.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if fee is payed on the receiving chain; I guess in that case as it is optionally payed, no assets are lost in case receiver does not support ics20-2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if fee is payed on the receiving chain;

It is paid on the receiving (destination) chain, the same time amount is paid.

Or do I misunderstand your comment?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, however this is fixable if all of the fee payout gets processed on the sender chain correct?

In the case where the acknowledgement does not contain receivePacket relayer, we simply refund the receive_fee


### Handshakes

Handshakes work much like `ics20-1` with some extensions.

Both machines `A` and `B` accept new channels from any module on another machine, if and only if:

- The channel being created is unordered.
- The version string is `ics20-1` OR `ics20-2`
- Both version and counterparty_version string are the same

The handshake implementation looks much like the above definition with the additional acceptance of `ics20-2` version string. Note that `ics20-2` implementations should store the channel version data to be used when creating packets to send (whether or not fee field is supported).

### Implementation

This is very similar to the `ics20-1` implementation, except for a few additions about fee handling. Note that `createOutgoingPacket` now needs both the extra `fee` information (set by the blockchain user), as well as the channel `version` (stored when the channel was created):

```typescript
function createOutgoingPacket(
denomination: string,
amount: uint256,
sender: string,
receiver: string,
source: boolean,
fee: Maybe<uint256>,
destPort: string,
destChannel: string,
sourcePort: string,
sourceChannel: string,
version: string,
timeoutHeight: Height,
timeoutTimestamp: uint64) {
prefix = "{sourcePort}/{sourceChannel}/"
// we are the source if the denomination is not prefixed
source = denomination.slice(0, len(prefix)) !== prefix
if source {
// determine escrow account
escrowAccount = channelEscrowAddresses[sourceChannel]
// escrow source tokens (assumed to fail if balance insufficient)
bank.TransferCoins(sender, escrowAccount, denomination, amount)
} else {
// receiver is source chain, burn vouchers
bank.BurnCoins(sender, denomination, amount)
}
// ADDED: we never set this field when talking with an ics20-1 chain
if version == "ics20-1"
fee = None
FungibleTokenPacketData data = FungibleTokenPacketData{denomination, amount, sender, receiver, fee}
handler.sendPacket(Packet{timeoutHeight, timeoutTimestamp, destPort, destChannel, sourcePort, sourceChannel, data}, getCapability("port"))
}
```

Likewise, `onRecvPacket` must distribute the fee (if set). This requires passing in the message signer field into the application:

```typescript
function onRecvPacket(packet: Packet, singer: string) {
FungibleTokenPacketData data = packet.data
// construct default acknowledgement of success
FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null}
prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
// we are the source if the packets were prefixed by the sending chain
source = data.denomination.slice(0, len(prefix)) === prefix
// ADDED: calculate recipient amount
toRcpt = data.amount - (data.fee || 0)
if source {
// receiver is source chain: unescrow tokens
// determine escrow account
escrowAccount = channelEscrowAddresses[packet.destChannel]
// unescrow tokens to receiver (assumed to fail if balance insufficient)
err = bank.TransferCoins(escrowAccount, data.receiver, data.denomination.slice(len(prefix)), toRcpt)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
// ADDED: handle fee distribution
if data.fee {
err = bank.TransferCoins(escrowAccount, signer, data.denomination.slice(len(prefix)), data.fee)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
}
} else {
prefix = "{packet.destPort}/{packet.destChannel}/"
prefixedDenomination = prefix + data.denomination
// sender was source, mint vouchers to receiver (assumed to fail if balance insufficient)
err = bank.MintCoins(data.receiver, prefixedDenomination, toRcpt)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
// ADDED: handle fee distribution
if data.fee {
err = bank.MintCoins(signer, prefixedDenomination, data.fee)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
}
}
return ack
}
```

The rest of the logic can remain unchanged. Note that the biggest changes are simply providing the additional information to the packet handlers - channel version to the sending logic, and message signer to the receive logic. Both the acknowledgement and timeout handlers can remain unchanged, as `amount` remains the total amount locked up in the sending chain.

## Backwards Compatibility

Not applicable.
All `ics20-1` packets are valid `ics20-2` packets. An `ics20-2` packet received by an `ics20-1` handler may be incorrectly processed (no funds sent to the relayer), but will not break any invariants (total number of tokens escrowed on sender == total number of tokens issued on receiver).

## Forwards Compatibility

This initial standard uses version "ics20-1" in the channel handshake.
This initial standard uses version "ics20-1" in the channel handshake. We also define a backwards-compatible "ics20-2" extension with minimal changes to the handlers.

A future version of this standard could use a different version in the channel handshake,
and safely alter the packet data format & packet handler semantics.
Expand All @@ -363,6 +497,8 @@ Feb 24, 2020 - Revisions to infer source field, inclusion of version string

July 27, 2020 - Re-addition of source field

May 24, 2020 - Added ICS20-2 extension

## Copyright

All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).