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: fleshing out ics21 spec #1

Draft
wants to merge 10 commits into
base: ics21
Choose a base branch
from
Draft

Conversation

spoo-bar
Copy link

@spoo-bar spoo-bar commented Sep 5, 2024

Render: https://github.com/noble-assets/ibc/tree/spoorthi/ics21-update/spec/app/ics-021-permissioned-token-transfer

Note: There are likely many typos. pls ignore for now 🙏🏻

Copy link

coderabbitai bot commented Sep 5, 2024

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

- Preservation of account permissions crosschain, which can forbid an account from
1. Sending the permissioned token
2. Receiving the permissioned token
3. Using the permmissioned token to pay for gas // todo ? is this needed?
Copy link
Author

@spoo-bar spoo-bar Sep 5, 2024

Choose a reason for hiding this comment

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

Is this needed as a desired property?

1. Sending the permissioned token
2. Receiving the permissioned token
3. Using the permmissioned token to pay for gas // todo ? is this needed?
4. Transferring the token back to Host Chain // todo ? should this be disallowed? Should even a blacklisted user be always able to send tokens to themselves on noble? and funds locked there
Copy link
Author

Choose a reason for hiding this comment

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

should this be disallowed? Should even a blacklisted user be always able to send tokens to themselves on noble? and funds locked there

Copy link
Member

Choose a reason for hiding this comment

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

Good call out. IMO, a blacklisted user should be able to send tokens back to their wallet on source, in which, it will then be blacklisted.

Copy link
Author

Choose a reason for hiding this comment

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

removed this requirement in 4cbea5e

The Host Chain is responsible for hosting a native token issuance mechanism. Any issued token denom should expose an Owner which controls the token issuance. Any number of ICS20 channels can be created between Host Chain and Mirror Chain for the transfer of these tokens. When the Owner decides to make the token permissioned, they register it with the ICS21 Host Module.

Once a Permissioned Token has been registered on the Host Chain, the Owner can now set the persmissions customization. This would include:
1. The ICS20 Channel IDs over which the Permissioned Token denom can be sent or received. // todo: is there any need to do receive check? should send check be enough? this will solve the potential issue in the 2nd paragraph below
Copy link
Author

Choose a reason for hiding this comment

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

is there any need to do receive check? should send check be enough? this will solve the potential issue in the 2nd paragraph below

counterpartyChannelIdentifier: Identifier,
version: string
): (version: string, err: Error) {
// only unordered channels allowed. this is bcuz ordered channels close on timeout
Copy link
Author

Choose a reason for hiding this comment

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

only unordered channels allowed. this is bcuz ordered channels close on timeout we dont want that. do we? but also, because we only submit the changeset of permissions and not the entire permission set, Ordered channels would make sense, to ensure n packet is accepted before n+1. e.g we add Alice to bloccklist in n, but remove Alice in n+1. if they are processed in wrong order, we will end up with state where Alice is still blocked

}
```

#### Channel Closing Flow
Copy link
Author

Choose a reason for hiding this comment

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

who can initiate channel close? just host? should mirror be able to exit as well (probably not). why would hosts want to close? should we have conditions that all AllowedChannels store should be empty (wrt channels on that chain) to be able to close. If not, Should we only allow the close of the channel if there are no ics20tokens of denom across the channel? Is this entrypoint hit when an ordered channels is closed due to timeout? will need to address channel closure due to client expiry anyway

if data.Signer != authtypes.NewModuleAddress(ics21types.ModuleName+data.CounterpartyChannelId) {
return NewErrorAcknowledgement(ics21types.ErrInvalidSigner)
}
/// todo: curent implementation also shares the whitelisted channel IDs to mirror. do we need to store this in Mirror? if we know a token is permissioned, cant we just ensure that it cant do ibc trasnfer to any chain except the source chain? i.e it can only return on the channel it came from, nothing else
Copy link
Author

Choose a reason for hiding this comment

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

current implementation also shares the whitelisted channel IDs to mirror. do we need to store this in Mirror? if we know a token is permissioned, cant we just ensure that it cant do ibc trasnfer to any chain except the source chain? i.e it can only return on the channel it came from, nothing else

}

switch typeof(acknowledgement) {
case *channeltypes.Acknowledgement_Error:
Copy link
Author

Choose a reason for hiding this comment

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

this is how the current implementation handles this, but in case of single Host but multiple Mirrors, this would mean, in case one channel failed to update but all others succeeded, the Host would still reset the state and end up with state mismatch.


```typescript
// Called on Host Chain by Relayer
function onTimeoutPacket(packet: Packet) {
Copy link
Author

Choose a reason for hiding this comment

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

same as above. in case of Ordered channels this would close the channel. should handle that. but in general, how to handle? re-attempt to send permissions update again?

Copy link
Member

Choose a reason for hiding this comment

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

A timed out packet would close the ics21 channel if channels are ordered?

Copy link
Author

Choose a reason for hiding this comment

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

yea its done to ensure the integrity of the sequence number. e.g if packet n times out, counterparty never learns about it. next packet will have n+1 seq number, and counterparty cant trust this packet cuz it doesnt know what happened to n packet.

permissions.ChannelAllowlist.pop(channel)
}

SetPermissions(denom, permissions)
Copy link
Author

Choose a reason for hiding this comment

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

should we consider initiating a new ICS21 channel here itself for every allowedChannel? this would mean for every Permissioned Token, there will be a dedicated channel. We could store a mapping of denom -> ics21 channels across all chains. Easy lookup for when permissions need to be updated everywhere. This would also allow to use Ordered Channels without ending up in a situation where the channel closes for all due to one timeout. so it reduces surface area of that kinda issues. we could trigger channel closes for removedChannels too

Copy link
Member

@boojamya boojamya left a comment

Choose a reason for hiding this comment

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

Awesome! You brought up some great points!
Some very thoughtful changes to how the implementation is currently going.

1. Sending the permissioned token
2. Receiving the permissioned token
3. Using the permmissioned token to pay for gas // todo ? is this needed?
4. Transferring the token back to Host Chain // todo ? should this be disallowed? Should even a blacklisted user be always able to send tokens to themselves on noble? and funds locked there
Copy link
Member

Choose a reason for hiding this comment

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

Good call out. IMO, a blacklisted user should be able to send tokens back to their wallet on source, in which, it will then be blacklisted.

1. Host Chain to Mirror Chain using a channel in ChanneAllowlist
2. Mirror Chain to Host Chain across the channel it came from

- Application of the permissions to tokens which have already been transferred before the configuration of ICS21 e.g multiple ics20 channels created and amount transferred before the creation of ICS21 channel. these should be addressed or at least identified
Copy link
Member

Choose a reason for hiding this comment

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

IMO, this is at the fault of the ICS21 implementor.
ICS21 should not be accountable for IBC transactions prior to the chain implementing the ics21 module(s).

spec/app/ics-021-permissioned-token-transfer/README.md Outdated Show resolved Hide resolved
Comment on lines 81 to 84
accountblocklist_additions: bytes[]
// accountblocklist_removals is the list of pubkeys removed from the blocklist with
// the new permissions update by the Owner
accountblocklist_removals: string[]
Copy link
Member

Choose a reason for hiding this comment

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

Should these be the same type?

Comment on lines 351 to 353
// Ensure the denom is registered with the x.bank module
denomExists = GetDenomFromBank(denom)
abortTransactionUnless(denomExists == true)
Copy link
Member

Choose a reason for hiding this comment

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

I think a chain should be allowed to permission a token that they haven't yet registered. I also think a chain should be able to permission a non native token.
However I can be swayed in either direction.

Copy link
Author

Choose a reason for hiding this comment

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

If permissions can be set for a token not yet registered with bank, could it allow Squatting? like if i know a new denom is launching on noble, I could just set myself as owner here right now 🤔

regarding setting non-native tokens to be permissioned, i am wondering if there could be potential issues if Host1 creates a permissioned token, sends it to Mirror1, and then mirror1 decides to set it as permissioned on itself as Host2. and what kinda issues this could create.

Comment on lines +369 to +372
// allowedChannels is a list of ICS20 channels IDs over which the denom can be sent over
allowedChannels: string[],
// removedChannels is a list of ICS20 channel IDs over which the denom cannot be sent over anymore
removedChannels: string[]
Copy link
Member

Choose a reason for hiding this comment

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

To reduce complexity, especially when updating the counterparty chain, maybe we separate the adding and removing of channels into separate messages/ibc packets?

This is at least how I currently have it implemented.

I also do not have this as a list of channels, and only allow the user to add or remove one channel at a time.
Do we envision users permssioning/forbidding multiple channels at once on one chain?

This would require a lot of checks and a lot of walking back state if say the update failed on the mirror chain.

But again, I can be convinced if we deem all this functionality should be combined.

Copy link
Author

Choose a reason for hiding this comment

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

I think the ChannelAllowlist needs to only exist on Host Chain. Because we arent not allowing multihops right. So the Mirror chain will forbid the token from going across any channel which is not the Host channel that it came from.
so not sure if this needs to be shared in an IBC packet


```typescript
// Called on Host Chain by Relayer
function onTimeoutPacket(packet: Packet) {
Copy link
Member

Choose a reason for hiding this comment

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

A timed out packet would close the ics21 channel if channels are ordered?

@spoo-bar
Copy link
Author

spoo-bar commented Sep 6, 2024

Hey @boojamya
Based on how we want to share the permission info with the Mirror

  1. Send entire denom permission in its entirely - this means we share more info, but this case we can use UNORDERED channels. By using unordered channels, we dont have to care about packet timeouts or about channel closing.
  2. Send only the permission changeset - this means we will need to use ORDERED channel. And this brings issues of timeouts. A bad actor could create an ICS21 token and attempt to update permissions with a timeout of 1second. and this will likely timeout. and thus close the channel. So in this situation, to reduce attack vector, we would need to use dedicated channels per permissioned token.

If we wanna use option 1, things can stay as they are. But I am pro option 2, cuz it involves lesser data transfer. (in case over time the permissions object could get very big that the relayer fees get too high or might even exceed max_tx_bytes )
But using option 2 would involve significant changes to the current implementation. So i just wanted to propose a possible solution and get your opinion (also, this should address the situation where a non native token needs to be permissioned)

ICS21 token flow

  1. The tokenfactory owner (or x/gov) executes a MsgRegisterPermissionedToken and sets an ics21 owner address
  2. The ics21 owner sets some channel permissions using MsgUpdateChannelAllowlist.
  for _, ics20channel in msg.allowedChannels {
     // check if its a valid ics20 channel with transfer port
     connectionID = connectionkeeper.GetConnectionByChannel(ics20channel)
     // check in local x/host state on if there is an ics21 channel for this denom on this connection already
     if exists, continue
     else, {
     // we force an ics21 channel creation immediately as we know we need it anyway
       port = ics21host-<denom>
       connectionid = ics20connectionID
       capability =  portkeeper.Bindport(port)
       claimcapability(port, capability)
       msg := channeltypes.NewMsgChannelOpenInit(port, "ics21-1", ORDERED, []string{connectionID}, "ics21mirror", owner)
       handler := k.msgRouter.Handler(msg)
       res, err := handler(ctx, msg)
       events := res.GetEvents()
       ctx.EventManager().EmitEvents(events) // need this so relayers can pick it up
     } 
  }
  1. The ICS21 owner now sets account permissions using MsgUpdateAccountBlocklist
 updatePacket = ics21types.SetAccountBlocklistPacket {
   denom = denom,
   accountblocklist_additions = addToBlocklist
   accountblocklist_removals = removeFromBlocklist
 }
 allics21channels =  hostkeeper.Get(denom)
 for _,ics21channel in allICS21channels {
   handler.SendPacketOnChannel(channel, updatePacket)
 }

Channel handshake

on chanopeninit -> parse denom from portid. check that denom is registered to be permissioned. ensure the signer is the token owner and not a random relayer. - this makes it permissioned. (but thats fine i think? because this channel is just for this denom)
on chanopentry -> parse denom ensure that channel does not exist.
on chanopenack -> hostkeeper.Set(denom, connectionid, ics21channelid) also set a queue in endblocker to update the permissions on this channel // this is to ensure if the ics21 is created after some permissions exist, the mirror is brought upto date
on chanopenConfirm -> mirrorKeeper.Set(baseDenom, connectionid, ics21channelid)

Relay

on RecvPacket - mirrorKeeper.Set(ics21channelid, permissions)

Mirror contract

With these two contracts, we can apply the ics21 permissions within the SendRestrictions as well as the ante handler

func IsPermissionedToken(denom) bool {
  permissions := GetTokenPermissions(denom)
  return permissoins != nil
}

func GetTokenPermissions(denom) {
  // denom will be ibc path as this is mirror
  basedenom, ics20ChannelID := transfertypes.ParseDenomTrace(denom)
  connectionID := ibcconnectionkeeper.GetConnection(ics20channelID)
  ics21channel := mirrorKeeper.Get(baseDenom, connectionID)
  permissions := mirrorKeeper.Get(ics21channel)
  return permissions
}

@boojamya
Copy link
Member

@spoo-bar, I really like the idea of initiating the ics21 handshake during the SetAllowedChannel message. Great idea! While this does open more channels it abstracts away the need to keep track and manually create these ics21 channels.

I've begun implementing here https://github.com/noble-assets/ics21/blob/733c23c6af2a782101b6678e362854ecada567c5/x/host/keeper/msg_server.go#L46-L106

While I've made some changes to state and the IBC channel handshake, the core idea of one channel per denom and initiating the channel handshake inside the SetAllowedChannel message seems to be working well.

Looking forward to discussing this more when you get back!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants