Skip to content

Commit

Permalink
v2Tao support in v1
Browse files Browse the repository at this point in the history
  • Loading branch information
sangier committed Oct 31, 2024
1 parent 7287aa1 commit 99eaf71
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 175 deletions.
12 changes: 1 addition & 11 deletions spec/app/ics-020-fungible-token-transfer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,14 @@ function onSendFungibleTokens(
}
}

var dataBytes bytes
transferVersion = payload.version
if transferVersion == "ics20-1" {
abortTransactionUnless(len(appData.tokens) == 1)
token = appData.tokens[0]
// abort if forwarding defined
abortTransactionUnless(appData.forwarding == nil)
// create v1 denom of the form: port1/channel1/port2/channel2/port3/channel3/denom
v1Denom = constructOnChainDenom(token.denom.trace, token.denom.base)
// v1 packet data does not support forwarding fields
data = FungibleTokenPacketData{v1Denom, token.amount, appData.sender, appData.receiver,appData.memo}
// specific econding packet data marshalling into bytes
dataBytes = payload.encoding.marshal(appData)
} else if transferVersion == "ics20-2" {
// create FungibleTokenPacket data
data = FungibleTokenPacketDataV2{tokens, appData.sender, appData.receiver, appData.memo, appData.forwarding}
// specific econding packet data marshalling into bytes
dataBytes = payload.encoding.marshal(appData)
// No-Op
} else {
// Unsupported transfer version
abortTransactionUnless(false)
Expand Down
248 changes: 84 additions & 164 deletions spec/app/ics-020-fungible-token-transfer/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ kind: instantiation
version compatibility: ibc-go v7.0.0, ibc-rs v0.53.0
author: Christopher Goes <[email protected]>
created: 2019-07-15
modified: 2020-02-24
modified: 2024-10-31
---

## Synopsis
Expand Down Expand Up @@ -113,123 +113,29 @@ interface ModuleState {

The sub-protocols described herein should be implemented in a "fungible token transfer bridge" module with access to a bank module and to the IBC routing module.

#### Port & channel setup
#### Application callback setup

The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module).
The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to register the application callbacks in the IBC router.

```typescript
function setup() {
capability = routingModule.bindPort("transfer", ModuleCallbacks{
onChanOpenInit,
onChanOpenTry,
onChanOpenAck,
onChanOpenConfirm,
onChanCloseInit,
onChanCloseConfirm,
onRecvPacket,
onTimeoutPacket,
onAcknowledgePacket,
onTimeoutPacketClose
})
claimCapability("port", capability)
IBCRouter.callbacks["transfer"]=[onSendPacket,onRecvPacket,onAcknowledgePacket,onTimeoutPacket]
}
```

Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the fungible token transfer module on separate chains.
Once the `setup` function has been called, the application callbacks are registered and accessible in the IBC router.

An administrator (with the permissions to create connections & channels on the host state machine) is responsible for setting up connections to other state machines & creating channels
to other instances of this module (or another module supporting this interface) on other chains. This specification defines packet handling semantics only, and defines them in such a fashion
that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time.

#### Routing module callbacks

##### Channel lifecycle management

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`.

```typescript
function onChanOpenInit(
order: ChannelOrder,
connectionHops: [Identifier],
portIdentifier: Identifier,
channelIdentifier: Identifier,
counterpartyPortIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
version: string) => (version: string, err: Error) {
// only unordered channels allowed
abortTransactionUnless(order === UNORDERED)
// assert that version is "ics20-1" or empty
// if empty, we return the default transfer version to core IBC
// as the version for this channel
abortTransactionUnless(version === "ics20-1" || version === "")
// allocate an escrow address
channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
return "ics20-1", nil
}
```

```typescript
function onChanOpenTry(
order: ChannelOrder,
connectionHops: [Identifier],
portIdentifier: Identifier,
channelIdentifier: Identifier,
counterpartyPortIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
counterpartyVersion: string) => (version: string, err: Error) {
// only unordered channels allowed
abortTransactionUnless(order === UNORDERED)
// assert that version is "ics20-1"
abortTransactionUnless(counterpartyVersion === "ics20-1")
// allocate an escrow address
channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
// return version that this chain will use given the
// counterparty version
return "ics20-1", nil
}
```

```typescript
function onChanOpenAck(
portIdentifier: Identifier,
channelIdentifier: Identifier,
counterpartyChannelIdentifier: Identifier,
counterpartyVersion: string) {
// port has already been validated
// assert that counterparty selected version is "ics20-1"
abortTransactionUnless(counterpartyVersion === "ics20-1")
}
```
##### Packet relay

```typescript
function onChanOpenConfirm(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
// accept channel confirmations, port has already been validated, version has already been validated
}
```
This specification defines packet handling semantics.

```typescript
function onChanCloseInit(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
// always abort transaction
abortTransactionUnless(FALSE)
}
```

```typescript
function onChanCloseConfirm(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
// no action necessary
}
```

##### Packet relay
Both machines `A` and `B` accept new packet from any module on another machine, if and only if the version string is `ics20-1`.

In plain English, between chains `A` and `B`:

Expand All @@ -240,130 +146,142 @@ In plain English, between chains `A` and `B`:
an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain
to take appropriate action based on the nature of the failure.

`sendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.
`onSendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.

denom: string
amount: uint256
sender: string
receiver: string
memo: string

```typescript
function sendFungibleTokens(
denomination: string,
amount: uint256,
sender: string,
receiver: string,
sourcePort: string,
sourceChannel: string,
timeoutHeight: Height,
timeoutTimestamp: uint64, // in unix nanoseconds
): uint64 {
prefix = "{sourcePort}/{sourceChannel}/"
function onSendFungibleTokens(
sourceChannelId:bytes,
payload: Payload
): bool {

// the decode function must check the payload.encoding is among those supported
success,appData=decode(payload.encoding,payload.appData)
abortTransactionUnless(success)

prefix = "{payload.sourcePort}/{sourceChannelId}/"
// we are the source if the denomination is not prefixed
source = denomination.slice(0, len(prefix)) !== prefix
source = appData.denom.slice(0, len(prefix)) !== prefix
if source {
// determine escrow account
escrowAccount = channelEscrowAddresses[sourceChannel]
escrowAccount = channelEscrowAddresses[sourceChannelId]
// escrow source tokens (assumed to fail if balance insufficient)
bank.TransferCoins(sender, escrowAccount, denomination, amount)
bank.TransferCoins(appData.sender, escrowAccount, appData.denom, appData.amount)
} else {
// receiver is source chain, burn vouchers
bank.BurnCoins(sender, denomination, amount)
bank.BurnCoins(appData.sender, appData.denom, appData.amount)
}

// create FungibleTokenPacket data
data = FungibleTokenPacketData{denomination, amount, sender, receiver}

// send packet using the interface defined in ICS4
sequence = handler.sendPacket(
getCapability("port"),
sourcePort,
sourceChannel,
timeoutHeight,
timeoutTimestamp,
json.marshal(data) // json-marshalled bytes of packet data
)

return sequence
return true
}
```

`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.

```typescript
function onRecvPacket(packet: Packet) {
FungibleTokenPacketData data = packet.data
assert(data.denom !== "")
assert(data.amount > 0)
assert(data.sender !== "")
assert(data.receiver !== "")
function onRecvPacket(
destChannelId: bytes,
sourceChannelId: bytes,
sequence: bigEndianUint64,
payload: Payload,
): (bytes,bool) {

success,appData=decode(payload.encoding,payload.appData)
abortTransactionUnless(success)

assert(appData.denom !== "")
assert(appData.amount > 0)
assert(appData.sender !== "")
assert(appData.receiver !== "")

// construct default acknowledgement of success
FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null}
prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
prefix = "{payload.sourcePort}/{sourceChannelId}/"
// we are the source if the packets were prefixed by the sending chain
source = data.denom.slice(0, len(prefix)) === prefix
source = appData.denom.slice(0, len(prefix)) === prefix
if source {
// receiver is source chain: unescrow tokens
// determine escrow account
escrowAccount = channelEscrowAddresses[packet.destChannel]
escrowAccount = channelEscrowAddresses[destChannelId]
// unescrow tokens to receiver (assumed to fail if balance insufficient)
err = bank.TransferCoins(escrowAccount, data.receiver, data.denom.slice(len(prefix)), data.amount)
err = bank.TransferCoins(escrowAccount, appData.receiver, appData.denom.slice(len(prefix)), appData.amount)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
} else {
prefix = "{packet.destPort}/{packet.destChannel}/"
prefixedDenomination = prefix + data.denom
prefixedDenomination = prefix + appData.denom
// sender was source, mint vouchers to receiver (assumed to fail if balance insufficient)
err = bank.MintCoins(data.receiver, prefixedDenomination, data.amount)
err = bank.MintCoins(appData.receiver, prefixedDenomination, appData.amount)
if (err !== nil)
ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
}
return ack
return ack,true
}
```

`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.

```typescript
function onAcknowledgePacket(
packet: Packet,
acknowledgement: bytes) {
sourceChannelId: bytes,
destChannelId: bytes, // Can be nullable
sequence: bigEndianUint64, // Can be nullable
payload: Payload,
acknowledgement: bytes
): bool {
// if the transfer failed, refund the tokens
if (!acknowledgement.success)
refundTokens(packet)
if (!acknowledgement.success){
refundTokens(sourceChannelId,payload)
}
return true
}
```

`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that it will not be received on the destination chain).

```typescript
function onTimeoutPacket(packet: Packet) {
function onTimeoutPacket(
sourceChannelId: bytes,
destChannelId: bytes, // Can be nullable
sequence: bigEndianUint64, // Can be nullable
payload: Payload
): bool {
// the packet timed-out, so refund the tokens
refundTokens(packet)
refundTokens(sourceChannelId,payload)
return true
}
```

`refundTokens` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original sender.

```typescript
function refundTokens(packet: Packet) {
FungibleTokenPacketData data = packet.data
prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
function refundTokens(
sourceChannelId: bytes,
payload: Payload
): bool {

success,appData=decode(payload.encoding,payload.appData)
abortTransactionUnless(success)

prefix = "{payload.sourcePort}/{sourceChannelId}/"
// we are the source if the denomination is not prefixed
source = data.denom.slice(0, len(prefix)) !== prefix
source = appData.denom.slice(0, len(prefix)) !== prefix
if source {
// sender was source chain, unescrow tokens back to sender
escrowAccount = channelEscrowAddresses[packet.srcChannel]
bank.TransferCoins(escrowAccount, data.sender, data.denom, data.amount)
escrowAccount = channelEscrowAddresses[sourceChannelId]
bank.TransferCoins(escrowAccount, appData.sender, appData.denom, appData.amount)
} else {
// receiver was source chain, mint vouchers back to sender
bank.MintCoins(data.sender, data.denom, data.amount)
bank.MintCoins(appData.sender, appData.denom, appData.amount)
}
}
```

```typescript
function onTimeoutPacketClose(packet: Packet) {
// can't happen, only unordered channels allowed
}
```

#### Using the Memo Field

Note: Since earlier versions of this specification did not include a `memo` field, implementations must ensure that the new packet data is still compatible with chains that expect the old packet data. A legacy implementation MUST be able to unmarshal a new packet data with an empty string memo into the legacy `FungibleTokenPacketData` struct. Similarly, an implementation supporting `memo` must be able to unmarshal a legacy packet data into the current struct with the `memo` field set to the empty string.
Expand Down Expand Up @@ -442,6 +360,8 @@ July 27, 2020 - Re-addition of source field

Nov 11, 2022 - Addition of a memo field

Oct 31, 2024 - [Support for IBC TAO V2](https://github.com/cosmos/ibc/pull/1157)

## Copyright

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

0 comments on commit 99eaf71

Please sign in to comment.