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

Adding in a new Authorization Type for IBC Transfers in Authz #2431

Closed
3 tasks
jonathanjmak opened this issue Sep 28, 2022 · 15 comments · Fixed by #3079
Closed
3 tasks

Adding in a new Authorization Type for IBC Transfers in Authz #2431

jonathanjmak opened this issue Sep 28, 2022 · 15 comments · Fixed by #3079
Labels
20-transfer type: feature New features, sub-features or integrations
Milestone

Comments

@jonathanjmak
Copy link

Summary

This is the same issue as cosmos/cosmos-sdk#13416, but now placing this request in the ibc-go repository so as to not introduce any circular dependencies.

Currently in the Cosmos SDK, the main types of authorization are stake, send, and generic authorization. With stake and send, there is an allowlist for authorization. However, if you wanted to authorize an IBC transfer, you would need to use generic authorization, which exposes unnecessarily loose permissions (because there is no allowlist, theoretically the authorized entity could IBC transfer to any address). We want to add in an authorization for IBC transfers to specific addresses via the allowlist.

Problem Definition

You don't want to use generic authorization for tasks outside of stake and send. It would make sense to add in an authorization type for IBC transfers given that is one of the most common use cases of the ecosystem. One example where this would be useful is authorizing an entity to programmatically conduct an IBC transfer to your existing wallets. Currently, you need to manually IBC transfer assets across chains yourself, but having this would allow you to easily IBC transfer assets across chains from an entity's app without the need to expose unnecessary authorizations.

Proposal

  1. A new authorization type IBCTransferAuthorization
  2. Parameters: AllowList, SpendLimit
    AllowList would contain a list of addresses authorized to receive IBC transferred assets
    SpendLimit is the amount that is authorized for each IBC transfer asset
  3. Optional Parameters: Port,Channel

You can make implementation almost identical to SendAuthorization except now you are also making a new authorization type for IBC Transfers.

Very similar to this issue that was accepted: cosmos/cosmos-sdk#12609
PR closing the above issue: cosmos/cosmos-sdk#12648

For Admin Use

  • Not duplicate issue
  • Appropriate labels applied
  • Appropriate contributors tagged/assigned
@crodriguezvega
Copy link
Contributor

Hi @jonathanjmak. Thanks for proposing this idea! I am trying to understand a bit how this would work, but I am not very familiar with x/authz, so forgive me if I am completely off or I ask stupid questions. :)

I guess you would like to be able to use x/authz CLI command or gRPC endpoint to grant authorization for the ICS20 transfers, right? If that's the case, I guess you would like to do something like, for example, grant cosmos1skjw.. ics20transfer --spend-limit=1000stake --allow-list=cosmos1md0k.. --from cosmos1k9ja.. using the CLI command. Is that right?

How would then this work if we want to avoid circular dependencies and shouldn't import ibc-go into the SDK? Because (in this example above using the CLI) eventually the SDK needs to instantiate an IBCTransferAuthorization (just like it is done for send here) and that type is defined in ibc-go...

I think that it would be possible though to use directly the x/authz gRPC endpoint if you create a MsgGrant and use the instance of IBCTransferAuthorization as the a parameter in NewMsgGrant, right?

And regarding the CLI, I guess we could create a separate grant-like CLI command in ibc-go to expose this functionality. Although that would mean that we have a two grant commands, one of them under ibc...

And regarding the optional Port and Channel parameters, should those be called destinationPort and destinationChannel to make it more explicit?

How does this sound? Looking forward to your feedback!

@crodriguezvega crodriguezvega added 20-transfer needs discussion Issues that need discussion before they can be worked on type: feature New features, sub-features or integrations labels Oct 3, 2022
@jonathanjmak
Copy link
Author

Hi @crodriguezvega, thanks for taking a look at this! I've addressed your points below. Hopefully they suffice and clear some things up (also open to feedback)

  1. Use x/authz CLI command or gRPC endpoint to grant authorization for the ICS20 transfers.
  • Correct, we want to be able to grant authorization to a different entity so that ICS20 transfers can happen through the authz module.
  1. How would then this work if we want to avoid circular dependencies? It would be possible though to use directly the x/authz gRPC endpoint?
  • I guess this is a good callout; it seems like one way is to have the Cosmos SDK team to also put it in their types as a new ibc_transfer_authorization.go as you mentioned. However, because of the circular dependencies, it seems like this wouldn't be possible as you mentioned.

  • Instead, one workaround to this is to define a type IBCTransferAuthorization only within ibc-go and then, by default, only allow this new transfer authorization to be called via gRPC on IBC enabled chains. (akin to your MsgGrant example). That should work as an implementation right?

  • In the future, we can add in the a grant-like CLI command in ibc-go to expose this functionality, but initially I think having this authorization type in ibc-go and only via gRPC endpoint can be enough to suffice for some initial use cases (having different entities be authorized for ibc transfers)

  1. Regarding the optional Port and Channel parameters, should those be called destinationPort and destinationChannel to make it more explicit?
  • Those sound fine to me, thanks!

@crodriguezvega
Copy link
Contributor

crodriguezvega commented Oct 6, 2022

Thanks for your comments, @jonathanjmak!

I was looking into this a bit more and trying to draft some code for discussion with the rest of the team.

So based on the discussion here so far I think the work would involve the following:

  • Creating an auth.proto file in proto/ibc/applications/transfer/v1 with this content:
syntax = "proto3";

package ibc.applications.transfer.v1;

option go_package = "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types";

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";

// TransferAuthorization allows the grantee to transfer up to spend_limit coins from
// the granter's account.
message TransferAuthorization {
  option (cosmos_proto.implements_interface) = "Authorization";

  repeated cosmos.base.v1beta1.Coin spend_limit = 1
      [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];

  // allow_list specifies an optional list of addresses to whom the grantee can transfer tokens on behalf of the
  // granter. If omitted, any recipient is allowed.
  repeated string allow_list = 2;

  // source_port and source_channel specify the port ID and channel ID on which tokens can be transferred.
  // If omitted, any combination of port ID and channel ID is allowed.
  string source_port = 3;
  string source_channel = 4;
}

I realize that in my previous comment I made a mistake by suggesting to use the names destinationPort and destinationChannel: they should actually be called sourcePort and sourceChannel.

  • Creating a transfer_authorization.go file in modules/apps/transfer/types with this content:
package types

import (
  sdk "github.com/cosmos/cosmos-sdk/types"
  sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
  "github.com/cosmos/cosmos-sdk/x/authz"

  host "github.com/cosmos/ibc-go/v6/modules/core/24-host"
)

// TODO: Revisit this once we have propoer gas fee framework.
// Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072
const gasCostPerIteration = uint64(10)

var _ authz.Authorization = &TransferAuthorization{}

// NewSendAuthorization creates a new SendAuthorization object.
func NewTransferAuthorization(spendLimit sdk.Coins, allowedAddrs []string, sourcePort, sourceChannel string) *TransferAuthorization {
  return &TransferAuthorization{
    SpendLimit:    spendLimit,
    AllowList:     allowedAddrs,
    SourcePort:    sourcePort,
    SourceChannel: sourceChannel,
  }
}

// MsgTypeURL implements Authorization.MsgTypeURL.
func (a TransferAuthorization) MsgTypeURL() string {
  return sdk.MsgTypeURL(&MsgTransfer{})
}

// Accept implements Authorization.Accept.
func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) {
  mTransfer, ok := msg.(*MsgTransfer)
  if !ok {
    return authz.AcceptResponse{}, sdkerrors.ErrInvalidType.Wrap("type mismatch")
  }
  toAddr := mTransfer.Receiver

  limitLeft, isNegative := a.SpendLimit.SafeSub(sdk.NewCoins(mTransfer.Token)...)
  if isNegative {
    return authz.AcceptResponse{}, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit")
  }
  if limitLeft.IsZero() {
    return authz.AcceptResponse{Accept: true, Delete: true}, nil
  }

  isAddrExists := false
  allowedList := a.GetAllowList()

  for _, addr := range allowedList {
    ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization")
    if addr == toAddr {
      isAddrExists = true
      break
    }
  }

  if len(allowedList) > 0 && !isAddrExists {
    return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("not allowed to transfer to address %s", toAddr)
  }

  if a.SourcePort != "" && a.SourcePort != mTransfer.SourcePort && a.SourceChannel != "" && a.SourceChannel != mTransfer.SourceChannel {
    return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("not allowed to transfer on port ID (%s) and channel ID (%s)", mTransfer.SourcePort, mTransfer.SourceChannel)
  }

  return authz.AcceptResponse{
    Accept: true,
    Delete: false,
    Updated: &TransferAuthorization{
      SpendLimit:    limitLeft,
      AllowList:     allowedList,
      SourcePort:    a.SourcePort,
      SourceChannel: a.SourceChannel,
    },
  }, nil
}

// ValidateBasic implements Authorization.ValidateBasic.
func (a TransferAuthorization) ValidateBasic() error {
  if a.SpendLimit == nil {
    return sdkerrors.ErrInvalidCoins.Wrap("spend limit cannot be nil")
  }
  if err := a.SpendLimit.Validate(); err != nil {
    return sdkerrors.Wrap(err, "invalid spend limit")
  }

  found := make(map[string]bool, 0)
  for i := 0; i < len(a.AllowList); i++ {
    if found[a.AllowList[i]] {
      return ErrDuplicateEntry
    }
    found[a.AllowList[i]] = true
  }

  if a.SourcePort != "" && a.SourceChannel != "" {
    if err := host.PortIdentifierValidator(a.SourcePort); err != nil {
      return sdkerrors.Wrap(err, "invalid source port ID")
    }

    if err := host.ChannelIdentifierValidator(a.SourceChannel); err != nil {
      return sdkerrors.Wrap(err, "invalid source channel ID")
    }
  }

  return nil
}

A lot of this code is similar to SendAuthorization in the Cosmos SDK, but I did change one thing: while in the ValidateBasic function of SendAuthorization it is checked that all coins in SpendLimit are positive, I have replaced that check with a more broad one using the Validate function of sdk.Coins.

  • Adding the ErrDuplicateEntry to modules/apps/transfer/types/errors.go:
ErrDuplicateEntry = sdkerrors.Register(ModuleName, 10, "duplicate entry")

I have NOT tested any of this code, so tests (both integration and probably e2e) need to be written to make sure that functionality is correct, but I hope this serves as a starting point for discussion to asses if this pseudo-implementation goes in the right direction.

@jonathanjmak Let me discuss with the rest of the team and I will comment back here. Just a couple of more questions for now:

  • The issue of using generic authorization is that you can only grant authorization to a message type, but you don't have any more granular control over the parameters of that specific message that you grant access to, right? So it's either you grant usage of the message or you don't; and if you grant usage of the message, then the grantee could send any token to any recipient on any portID/channelID, right?
  • If the team agrees that the approach outlined here looks initially correct, would you be interested in working on this and open a PR?

@jonathanjmak
Copy link
Author

This looks really good @crodriguezvega ! Really appreciate you getting back to us so quickly on this. The initial code outline looks pretty solid, and nice thought to already add in the error reporting for duplicate account addresses. I also think renaming it back to sourcePort/Channel instead destinationPort/Channel will save a query, so that's good and I think would be better.

Question answers below:

  1. Regarding generic authorization -- that's correct, and I think that's why we wanted to make this
    more specific authorization so you're not exposing any additional freedom to the authorized entity than need be.

  2. More than happy to help out with this and draft up the PR! Looking forward to getting this implemented.

@jonathanjmak
Copy link
Author

Hi @crodriguezvega , just wanted to follow up on this to see if the team had any additional thoughts!

@crodriguezvega
Copy link
Contributor

Hi @crodriguezvega , just wanted to follow up on this to see if the team had any additional thoughts!

Hi @jonathanjmak. Sorry I didn't come back to you earlier... The team has been pretty busy and I think we're still going to need some time until we can discuss this issue. Unfortunately it might take a couple of weeks... I am very sorry, but I hope you can understand and give us some more time.

@jonathanjmak
Copy link
Author

Hi @crodriguezvega, no worries -- I understand! I'll give you another ping in a few weeks, but in the meantim let me know if there is anything I can do to assist you further on this discussion.

@jz5003
Copy link

jz5003 commented Nov 15, 2022

@crodriguezvega Any update from the team?

@crodriguezvega
Copy link
Contributor

Hi @jz5003. Sorry for not replying earlier. We have asked the SDK team to review the proposal to make sure that it all looks good. I will try to get their approval as soon as possible, so that work can be started on this. Sorry again for the delay. Is there any timeline where you would like to have this in a release of ibc-go?

@jonathanjmak
Copy link
Author

hi @crodriguezvega , preferably by the end of the year would be great! We'd love to integrate this since we think it will add in a lot of value and help make protocol interactions a lot smoother.

@crodriguezvega
Copy link
Contributor

This PR has been opened to implement it! @jonathanjmak @jz5003 please take a look and leave any feedback you may have. The implementation in the PR is slightly different than what we discussed here (the authorization could be granted for one or multiple channels at the same time), but hopefully that would also work for you.

@jonathanjmak
Copy link
Author

Hi @crodriguezvega , thanks for keeping us updated. I took a look and left a few comments. Let me know what I can do to help push this to the finish line!

@crodriguezvega crodriguezvega removed the needs discussion Issues that need discussion before they can be worked on label Dec 20, 2022
@crodriguezvega
Copy link
Contributor

crodriguezvega commented Dec 20, 2022

Requirements for this feature:

  • A granter shall be able to grant a grantee the privilege to submit MsgTransfer on its behalf.
  • The granter shall be able to grant privileges to transfer funds on a set (of 1 or more elements) of specific (and unique) source port ID/channel ID pairs.
  • For each source port ID/channel ID pair, the granter shall be able to specify a spend limit for each denomination they wish to allow the grantee to be able to transfer.
  • The granter shall be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed.
  • The granter shall be able to grant privileges to transfer funds on all source port ID/channel ID pairs.

@jonathanjmak Do you agree with the requirements listed above?

We will pick up this work in January and start opening PRs against the feature branch.

@crodriguezvega crodriguezvega moved this to Todo in ibc-go Dec 20, 2022
@crodriguezvega crodriguezvega added this to the v7.0.0 milestone Dec 21, 2022
@crodriguezvega
Copy link
Contributor

@jonathanjmak We have picked up this work now and we have realized that implementing this requirement

  • The granter shall be able to grant privileges to transfer funds on all source port ID/channel ID pairs.

will add extra complexity that can cause bugs in the future. Therefore we would like to waive this requirement for now and revisit later on, once the feature is released, if it is still needed.

Do you agree with this approach?

@crodriguezvega
Copy link
Contributor

@jonathanjmak We have completed the feature and it's available in this branch, in case you want to have a look and do any testing and/or early integration. We will release this in v7.

@damiannolan damiannolan moved this from Todo to Done in ibc-go Feb 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
20-transfer type: feature New features, sub-features or integrations
Projects
Status: Done 🥳
Development

Successfully merging a pull request may close this issue.

3 participants