Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
docs: ADR-001 Transfer Module Implementation (#136)
Browse files Browse the repository at this point in the history
* docs: ADR-001 Transfer Module Implementation

* chore: nits + link to creat_token method

* add concrete example among three roll-ups

* spellings

* fix: add trace prefix check condition when token is IBC-created

* update scenario table

* update ADR01 examples

* grammar

* update text

* markdown fmt

* grammar

* few more changes

* change order of rpc section

* update example section

---------

Co-authored-by: Ranadeep Biswas <[email protected]>
  • Loading branch information
Farhad-Shabani and rnbguy authored Apr 13, 2024
1 parent d4054cd commit 56ecb53
Showing 1 changed file with 233 additions and 0 deletions.
233 changes: 233 additions & 0 deletions docs/architecture/adr-001-transfer-module-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# ADR 001 - ICS-20 Transfer Module Implementation

## Changelog

- 2024-04-10: ADR Drafted

## Status

Implemented

## Context

This ADR outlines the implementation of the ICS-20 transfer module within the
Sovereign SDK system using the `ibc-rs` library.

we established a module struct to integrate the ICS-20 implementation from
`ibc-rs` into the Sovereign SDK, particularly to implement the consumer traits
of `ibc-rs`. This struct's primary role is to provide keys for accessing storage
to get/set IBC relevant states. We named this struct `IbcTransfer`, residing
within its own standalone crate called `sov-ibc-transfer`. It is annotated with
the `ModuleInfo` derive, which registers `IbcTransfer` as a module within the
Sovereign SDK system.

It's important to note that the `IbcTransfer` module was developed independently
from `Ibc`. In the context of Sovereign SDK, each struct implementing
`ModuleInfo` represents a distinct module within the system. This modular
approach allows for more efficient integration, initialization, and operation of
modules. Initially, we considered combining these modules under a single entity,
but that approach would complicate dependency and feature management. (See PR#14
for more details)

## Entry Point

Despite comprising two distinct modules, the entry point for both is `Ibc`
module. This module plays a pivotal role in handling incoming IBC messages. This
necessity stems from the operational structure of `ibc-rs` handlers, where the
processing of ICS-20 packets relies on core handlers accessible exclusively
through `Ibc`.

Upon receiving a message, `Ibc` proceeds to identify its type, such as ICS-20
packets, and directs it to the corresponding module. Specifically, in our setup,
this entails invoking the
[`transfer`](https://github.com/informalsystems/sovereign-ibc/blob/0c3b99f44613ff9a8668ade798b39507a17a7321/modules/sov-ibc/src/call.rs#L66)
method to route the ICS-20 messages to the `IbcTransfer` for subsequent handling
and processing.

## Module Structure

In addition to its core functionalities, `IbcTransfer` maintains a record of
tokens minted on a rollup created by the transfer module. You can see details of
this structure
[here](https://github.com/informalsystems/sovereign-ibc/blob/4e37dc4bb88624765384d1662549c00e991acc4a/modules/sov-ibc-transfer/src/lib.rs#L20-L50)
in the `sov-ibc-transfer` crate.

Specifically, `IbcTransfer` manages two essential maps:

- `minted_token_name_to_id`: This map links the token name to its corresponding
token ID for tokens created by IBC. It is used during the minting and burning
processes to check if the token exists and to obtain the necessary ID for
these operations.

- `minted_token_id_to_name`: This map connects the token ID to its corresponding
token name for tokens created by IBC. It is utilized during escrow and
un-escrow processes to confirm that the `TokenId` obtained from the `denom` is
**not** an IBC-created token, indicating it is a native token for escrow and
un-escrow operations.

If the purpose of these maps is not entirely clear, reviewing how each transfer
scenario is handled in the following section will provide clarity.

## Transfer Scenarios

Given the context provided, the `IbcTransfer` module handles four scenarios,
each comprising validation _(x_validate method)_ and execution _(x_execute
method)_ stages.

Before diving into the specifics of each scenario, it is essential to understand
that the token name on the Sovereign SDK rollups is not guaranteed to be unique,
and hence when transferring **native tokens** we must use the token ID (which is
guaranteed to be unique) as the ICS-20 denom to ensure uniqueness.

### Escrowing Tokens - Sender on Rollup with Rollup as Source

1. Verify that the `memo` field does not exceed the maximum allowed length to
prevent large memos from overwhelming the system. We set the maximum memo
length to 32768 (2^15) bytes like the `ibc-go`.
2. Identify the token ID by parsing the `denom` field of the receiving
`MsgTransfer`.
3. Validate that the token is native and **not** an IBC-created token by
cross-referencing with the `minted_token_id_to_name` state.
- If the token ID is found in the `minted_token_id_to_name` state, check if
the corresponding token name begins with the trace path
`<given_port_id>/<given_channel_id>/`. If it does, reject the transfer.
4. Confirm the sender has a sufficient balance.
5. Retrieve the escrow address for the specified port and channel pair from the
cache. If absent, compute and cache the address. Utilize caching to avoid
repeated computations.
6. Execute the transfer function of the `bank` module to escrow tokens into the
designated escrow account.

### Unescrowing Tokens - Receiver on Rollup with Rollup as Source

1. Obtain the base denom by removing the prefix. In the receiving process,
`ibc-rs` automatically removes the first prefix. For instance, if a token
with the denom `my_token` was previously sent on channel `channel-0` and port
`transfer` (on the counterparty), it will be received in `recv_packet` as
`transfer/channel-0/my_token`. `ibc-rs` strips `transfer/channel-0/` from the
denom, so `coin.denom` would be `my_token` for unescrowing.
2. Validate that the token is native and **not** an IBC-created token by
referencing the `minted_token_id_to_name` state.
- If the token ID is found in the `minted_token_id_to_name` state, check if
the corresponding token name begins with the trace path
`<given_port_id>/<given_channel_id>/`. If it does, reject the transfer.
This step only fails when the counterparty chain produces a malicious IBC
transfer `send_packet()`.
3. Obtain the escrow address for a specified port and channel pair, similar to
the escrowing step.
4. Verify that the escrow account has a sufficient balance.
5. Unescrow the token from the escrow account to the receiver's address.

### Minting Tokens - Receiver on Rollup with Sender as Source

1. Obtain the full denom by prefixing it (e.g., `transfer/channel-0/uatom`).
`ibc-rs` handles prefixing a base denom with the specified port and channel
IDs.
2. Retrieve the token ID by checking if a token for the given denom has been
previously created by the IBC module using the `minted_token_name_to_id` map.
- If yes, use that `TokenId`.
- If no,
[create a new token](https://github.com/informalsystems/sovereign-ibc/blob/4e37dc4bb88624765384d1662549c00e991acc4a/modules/sov-ibc-transfer/src/context.rs#L105)
with the name set to the `denom`, obtain the token ID, and store the
ordered pair of _token name_, _token ID_, and its reverse pair in the
`minted_token_name_to_id` and `minted_token_id_to_name` state maps
respectively. These two state maps maintain a bijection between _token
name_ and _token ID_.
- NOTE: When IBC initiates the creation of a new token, the `IbcTransfer`
address is designated as the authorized minter.
- NOTE: In these steps, we ensure that the `context` object needed for token
creation uses the `ibc_transfer` address as the `sender` by constructing a
new context object.
3. Mint tokens to the receiver's address with the specified amount in the
`MsgRecvPacket` message.

### Burning Tokens - Sender on Rollup with Receiver as Source

1. Verify that the `memo` field does not exceed the maximum allowed length to
prevent large memos from overwhelming the system. We set the maximum memo
length to 32768 (2^15) bytes like the `ibc-go`.
2. Obtain the `TokenId` using the denom from the `minted_token_name_to_id` map.
- If the token ID is not found, the transfer is rejected.
3. Confirm that the sender has a sufficient balance.
4. Burn tokens from the sender's address by calling `burn` on the `bank` module.

Therefore, as a primary rule, when transferring a native token, the token ID is
used as the denom. For IBC-created tokens, the regular prefixed denomination is
utilized as the denom. This may pose a challenge for front-ends to correctly
identify the token type when crafting the related appropriate transfer message.
Here existing RPC methods can assist.

## Available RPC Methods

To facilitate the interaction with the `IbcTransfer` module, two RPC methods are
available:

- `transfer_mintedTokenName`: Queries the `minted_token_id_to_name` state to
retrieve the token name for a given token ID.
- `transfer_mintedTokenId`: Queries the `minted_token_name_to_id` state to
obtain the token ID for a given token name.

Additionally, worth noting there is an RPC method as `transfer_moduleId` that
returns the address of the `IbcTransfer` module.

## Example Scenarios among Three Sovereign Rollups

Since the translations between _token name_ and _token ID_ became nuanced, we
will go through a few example scenarios - where the ICS20 application is
deployed on three Sovereign rollups `sovA`, `sovB`, and `sovC`; and they are
interconnected as shown below:

```
chAB┌────┐chAC
┌───────┤sovA├───────┐
│ └────┘ │
│ │
chBA┌─┴──┐chBC chCB┌──┴─┐chCA
│sovB├──────────────┤sovC│
└────┘ └────┘
```

We will consider the scenarios when sending `tokA` (a token native to `sovA`) in
this route:

`sovA` -> `sovB` -> `sovC` -> `sovA` -> `sovC` -> `sovB` -> `sovA`

That is, we do a round trip of `tokA` starting and ending at `sovA` via `sovB`
and `sovC`; and then we unwind the round trip.

The following table shows the mappings between Sovereign native tokens and IBC
denom traces for each scenario:

| source rollup | source channel | denom in `MsgTransfer` and denom in ICS20 packet | is target source? | native token on target | ibc denom trace on target |
| :-----------: | :------------: | :----------------------------------------------: | :---------------: | :--------------------: | :--------------------------: |
| `sovA` | `chAB` | `tokA` | no | `tokA_onB` | `transfer/chBA/tokA` |
| `sovB` | `chBC` | `tokA_onB` | no | `tokA_onB_onC` | `transfer/chCB/tokA_onB` |
| `sovC` | `chCA` | `tokA_onB_onC` | no | `tokA_onB_onC_onA` | `transfer/chAC/tokA_onB_onC` |
| `sovA` | `chAC` | `transfer/chAC/tokA_onB_onC` | yes | `tokA_onB_onC` | `transfer/chCB/tokA_onB` |
| `sovC` | `chCB` | `transfer/chCB/tokA_onB` | yes | `tokA_onB` | `transfer/chBA/tokA` |
| `sovB` | `chBA` | `transfer/chBA/tokA` | yes | `tokA` | - |

Note that, `MsgTransfer` on the Sovereign `IBC` module takes an IBC denom trace
when sending it back via its originating channel, otherwise, it takes a native
token. This means that _mint_ and _burn_ methods take an IBC denom trace, while
_escrow_ and _unescrow_ methods take a native token.

| method | denom type | trigger | condition |
| :------: | :--------: | :-----------: | :---------------------------------------------------------------: |
| mint | ibc | `recv_packet` | - |
| burn | ibc | `MsgTransfer` | IBC denom must originate from the current channel* |
| escrow | native | `MsgTransfer` | corresponding IBC denom can't originate from the current channel* |
| unescrow | native | `recv_packet` | - |

(*_the current channel_: the channel where the ICS20 packet will be sent to.)

## References

Here are a list of relevant issues and PRs:

- Review `sov-ibc-transfer` implementation and apply fixes
[#133](https://github.com/informalsystems/sovereign-ibc/pull/133)
- Token transfer escrow/unescrow + mint/burn tests
[#47](https://github.com/informalsystems/sovereign-ibc/pull/47)
- Split `sov-ibc` from `sov-ibc-transfer`
[#14](https://github.com/informalsystems/sovereign-ibc/pull/14)

0 comments on commit 56ecb53

Please sign in to comment.