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

docs: ics20 hooks #2465

Merged
merged 13 commits into from
Mar 20, 2024
117 changes: 115 additions & 2 deletions x/uibc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,133 @@

## Content

- [IBC Quota](#ibc-quota)
- [IBC ICS20 Hooks](#ibc-ics20-hooks)
- [IBC ICS20 Quota](#ibc-isc20-quota)

Check failure on line 12 in x/uibc/README.md

View workflow job for this annotation

GitHub Actions / markdown-lint

Link fragments should be valid

x/uibc/README.md:12:3 MD051/link-fragments Link fragments should be valid [Context: "[IBC ICS20 Quota](#ibc-isc20-quota)"] https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md051.md
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

## IBC Quota
## IBC ICS20 Hooks

The IBC ICS20 hooks are part of our [ICS20 middleware](https://github.com/umee-network/umee/blob/main/x/uibc/uics20/ibc_module.go#L25) that enables ICS-20 token transfers to trigger message execution. This functionality allows cross-chain calls that involve token movement. IBC hooks are useful for a variety of use cases, including cross-chain swaps, which are an extremely powerful primitive.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

### Concepts

Users can define ICS20 hook instructions in ICS20 transfer Memo field, that will trigger procedure call once the transfer is successfully recorded in the UX Chain.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

### Design

The ICS20 packet data Memo field (introduced in [IBC v3.4.0](https://medium.com/the-interchain-foundation/moving-beyond-simple-token-transfers-d42b2b1dc29b)) allows to attach arbitrary data to a token transfer. The hook execution will be triggered if and only if:
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

- the packet data `memo` field can be JSON deserialized into the [`umee/uibc/v1/ICS20Memo`](https://github.com/umee-network/umee/blob/v6.4.0-beta1/proto/umee/uibc/v1/uibc.proto#L14). This means that the JSON serialized object into the memo string must extend the ICS20Memo struct.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
- `ICS20Memo.fallback_addr`, if defined, must be a correct bech32 Umee address.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

The fallback address is optional. It used when the memo is valid, but the hook execution (messages) fail. We strongly recommend to always use it. Otherwise the funds can be stuck in the chain if the hook execution fails.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
If memo has a correct structure, and fallback addr is defined but malformed, we cancel the transfer (otherwise we would not be able to use it correctly).

The hooks processing has the following flow:

- Check ICS20 Quota. If quota exceeds, the transfer is cancelled.
- Deserialize Memo into `ICS20Memo`. If fails, assume the memo doesn't have hook instructions and continue with a normal transfer.
- Validate `ICS20Memo.fallback_addr`. If fails, the transfer is cancelled.
- Unpack and validate `ICS20Memo.messsages` (procedures). If fails, continue with the transfer, and overwrite the recipient with the `fallback_addr` if it is defined.
- Execute the transfer.
- Execute the hooks messages. If they fail and `fallback_addr` is defined, then revert the transfer (and all related state changes and events) and use send the tokens to the `fallback_addr` instead.

### Proto

```protocol-buffer
message ICS20Memo {
// messages is a list of `sdk.Msg`s that will be executed when handling ICS20 transfer.
repeated google.protobuf.Any messages = 1;
// fallback_addr [optional] is a bech23 account address used to overwrite the original ICS20
// recipient as described in the Design section above.
string fallback_addr = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}
```

### Supported Messages

The `ICS20Memo` is a list of native `sdk.Message`. Only the following combinations are supported currently:
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

- `[MsgSupply]`
- `[MsgSupplyCollateral]`
- `[MsgLiquidate]`

Validation:

- the operator (defined as the message signer) in each message, must be the same as the ICS20 transfer recipient,
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
- messages must only use the subset of the transferred tokens.

NOTE: because the received amount of tokens may be different than the amount originally sent (relayers or hop chains may charge transfer fees), if the amount of tokens in each message exceeds the amount received, we adjust the token amount in the messages.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

### Example

Example 1: valid Memo Hook. Send `400 ibc/C0737D24596F82E8BD5471426ED00BDB5DA34FF13BE2DC0B23F7B35EA992B5CD` (here, the denom is an IBC denom of remote chain representing `uumee`). Execute a hook procedure to supply and collateralize `312uumee` and the reminder amount will be credited to the recipient balance.

```json
{
"fallback_addr": "umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu",
"messages": [
{
"@type": "/umee.leverage.v1.MsgSupplyCollateral",
"supplier": "umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due",
"asset": { "denom": "uumee", "amount": "312" }
}
]
}
```

Command to make a transaction:

```sh
umeed tx ibc-transfer transfer transfer channel-1 umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due \
400ibc/C0737D24596F82E8BD5471426ED00BDB5DA34FF13BE2DC0B23F7B35EA992B5CD \
--from umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due \
--memo '{"fallback_addr":"umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu","messages":[{"@type":"/umee.leverage.v1.MsgSupplyCollateral","supplier":"umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due","asset":{"denom":"uumee","amount":"312"}}]}'
```

Example 2: memo struct is correct, struct is correct, but it doesn't pass the validation. The procedure wants to use `uother` denom, but the transfer sends `uumee` coins. Since the `fallback_addr` is correctly defined, all coins will go to the `fallback_addr`.

```json
{
"fallback_addr": "umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu",
"messages": [
{
"@type": "/umee.leverage.v1.MsgSupplyCollateral",
"supplier": "umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due",
"asset": { "denom": "uother", "amount": "312" }
}
]
}
```

Command to make a transaction:

```sh
$ umeed tx ibc-transfer transfer transfer channel-1 umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due \
400ibc/C0737D24596F82E8BD5471426ED00BDB5DA34FF13BE2DC0B23F7B35EA992B5CD \
--from umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due \
--memo '{"fallback_addr":"umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu","messages":[{"@type":"/umee.leverage.v1.MsgSupplyCollateral","supplier":"umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due","asset":{"denom":"uother","amount":"312"}}]}'
```

### Compatibility with IBC Apps Hooks

The IBC Apps repo has [`ibc-hooks`](https://github.com/cosmos/ibc-apps/tree/main/modules/ibc-hooks) middleware, which has similar functionality and the Memo structure is compatible with the one defined here. IBC App hooks only support cosmwasm procedures: the instruction is defined in the `Memo.wasm` field and fallback is fully handled by the CW contract. This implementation is compatible with IBC Apps: in the future we can support IBC Apps `wasm` hooks, without breaking our `Memo` struct.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

### Limitations

- The current protocol requires that the IBC receiver is same as the "operator" (supplier, liquidator) in the `Memo.messages`.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

## IBC ICS20 Quota

Hack or lending abuse is impossible to stop once the funds leave the chain. One mitigation is to limit the IBC inflows and outflows and be able to stop a chain and recover the funds with a migration.

robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
### Concepts

Check failure on line 130 in x/uibc/README.md

View workflow job for this annotation

GitHub Actions / markdown-lint

Multiple headings with the same content

x/uibc/README.md:130 MD024/no-duplicate-heading Multiple headings with the same content [Context: "### Concepts"] https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md024.md

Inflow is an ICS-20 transaction of sending tokens to the Umee chain.
Outflow is an ICS-20 transaction sending tokens out of the Umee chain.

IBC Quota is an upper limit in USD amount.

### Design

Check failure on line 137 in x/uibc/README.md

View workflow job for this annotation

GitHub Actions / markdown-lint

Multiple headings with the same content

x/uibc/README.md:137 MD024/no-duplicate-heading Multiple headings with the same content [Context: "### Design"] https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md024.md

All inflows and outflows are measured in token average USD value using our x/oracle `AvgKeeper`. The `AvgKeeper` aggregates TVWAP prices over 16h window.

Expand Down
5 changes: 3 additions & 2 deletions x/uibc/uics20/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ func NewICS20Module(app porttypes.IBCModule, cdc codec.JSONCodec, k quota.Keeper
// validation, then we continue with the transfer and overwrite the original receiver to
// fallback_addr if it's defined.
// 4. Execute the downstream middleware and the transfer app.
// 5. Execute hooks. If hook execution fails, we don't use the the fallback_addr nor ignore the
// transfer. This is because there could be other middlewares that are already executed.
// 5. Execute hooks. If hook execution fails, and the fallback_addr is defined, then we revert
// the transfer (and all related state changes and events) and use send the tokens to the
// `fallback_addr` instead.
func (im ICS20Module) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress,
) exported.Acknowledgement {
ftData, err := deserializeFTData(im.cdc, packet)
Expand Down
1 change: 1 addition & 0 deletions x/uibc/uics20/memo_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func (mh *MemoHandler) onRecvPacketPrepare(
sdkerrors.Wrap(err, "ICS20 memo fallback_addr defined, but not formatted correctly")
}
if mh.fallbackReceiver.Equals(mh.receiver) {
// no need to specify fallback address if it's the same as the original receiver
mh.fallbackReceiver = nil
}
}
Expand Down
Loading