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

imp: allow memo strings instead of keys for transfer authorizations #6268

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (apps/27-interchain-accounts) [\#5533](https://github.com/cosmos/ibc-go/pull/5533) ICA host sets the host connection ID on `OnChanOpenTry`, so that ICA controller implementations are not obliged to set the value on `OnChanOpenInit` if they are not able.
* (core/02-client, core/03-connection, apps/27-interchain-accounts) [\#6256](https://github.com/cosmos/ibc-go/pull/6256) Add length checking of array fields in messages.
* (apps/27-interchain-accounts, apps/tranfer, apps/29-fee) [\#6253](https://github.com/cosmos/ibc-go/pull/6253) Allow channel handshake to succeed if fee middleware is wired up on one side, but not the other.
* (apps/transfer) [\#6268](https://github.com/cosmos/ibc-go/pull/6268) Use memo strings instead of JSON keys in `AllowedPacketData` of transfer authorization.
Copy link
Member

Choose a reason for hiding this comment

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

State machine breaking, right?


### Features

Expand Down
7 changes: 3 additions & 4 deletions docs/docs/02-apps/01-transfer/08-authorizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ It takes:

- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`.

- an `AllowedPacketData` list that specifies the list of memo packet data keys that are allowed to send the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed.
- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed.
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding the article "the" before "memo field" for grammatical correctness.

- then any content in `memo` field will be allowed.
+ then any content in the `memo` field will be allowed.

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed.
- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in the `memo` field will be allowed.


Setting a `TransferAuthorization` is expected to fail if:

Expand Down Expand Up @@ -51,9 +51,8 @@ type Allocation struct {
SpendLimit sdk.Coins
// allow list of receivers, an empty allow list permits any receiver address
AllowList []string
// allow list of packet data keys, an empty list prohibits all packet data keys;
// a list only with "*" permits any packet data key
// allow list of memo strings, an empty list prohibits all memo strings;
// a list only with "*" permits any memo string
AllowedPacketData []string
}

```
4 changes: 2 additions & 2 deletions modules/apps/transfer/types/authz.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion modules/apps/transfer/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
// DenomPrefix is the prefix used for internal SDK coin representation.
DenomPrefix = "ibc"

// AllowAllPacketDataKeys holds the string key that allows all packet data keys in authz transfer messages
// AllowAllPacketDataKeys holds the string key that allows all memo strings in authz transfer messages
AllowAllPacketDataKeys = "*"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe this constant can also be renamed in v9? We could also add a new constant with a more accurate name for the back ports.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, maybe you can rename it on this PR and then change it back in the backport

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am going to rename it in a followup PR, just to make the backport easier.


KeyTotalEscrowPrefix = "totalEscrowForDenom"
Expand Down
43 changes: 15 additions & 28 deletions modules/apps/transfer/types/transfer_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import (
"context"
"encoding/json"
"math/big"
"sort"
"slices"
"strings"

"github.com/cosmos/gogoproto/proto"
Expand Down Expand Up @@ -155,9 +154,9 @@
}

// validateMemo returns a nil error indicating if the memo is valid for transfer.
func validateMemo(ctx sdk.Context, memo string, allowedPacketDataList []string) error {
func validateMemo(ctx sdk.Context, memo string, allowedMemos []string) error {
// if the allow list is empty, then the memo must be an empty string
if len(allowedPacketDataList) == 0 {
if len(allowedMemos) == 0 {
if len(strings.TrimSpace(memo)) != 0 {
return errorsmod.Wrapf(ErrInvalidAuthorization, "memo must be empty because allowed packet data in allocation is empty")
}
Expand All @@ -166,39 +165,27 @@
}

// if allowedPacketDataList has only 1 element and it equals AllowAllPacketDataKeys
// then accept all the packet data keys
if len(allowedPacketDataList) == 1 && allowedPacketDataList[0] == AllowAllPacketDataKeys {
// then accept all the memo strings
if len(allowedMemos) == 1 && allowedMemos[0] == AllowAllPacketDataKeys {
return nil
}

jsonObject := make(map[string]interface{})
err := json.Unmarshal([]byte(memo), &jsonObject)
if err != nil {
return err
}

gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat

for _, key := range allowedPacketDataList {
isMemoAllowed := slices.ContainsFunc(allowedMemos, func(allowedMemo string) bool {
ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization")

_, ok := jsonObject[key]
if ok {
delete(jsonObject, key)
if strings.TrimSpace(memo) == strings.TrimSpace(allowedMemo) {
crodriguezvega marked this conversation as resolved.
Show resolved Hide resolved
crodriguezvega marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Thinking more about this, I think trim spaces doesn't make much sense in here. We are allowing a specific memo string, and not making any assumptions about the format such as it being json. It makes no sense that if a user allowed "rent payment", then they also allow "rentpayment". Especially if these transactions would be indexed by an indexer later based on the memo.

Maybe worth asking others, but I'm starting to feel more strongly about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TrimSpace only trims leading and trailing spaces, not spaces in between the text. That's why I thought that it would make sense to use it.

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, I'm back to being indifferent about it then. Still ran be removed imo. We shouldn't be cleaning up user mess XD

return true
} else {

Check failure on line 179 in modules/apps/transfer/types/transfer_authorization.go

View workflow job for this annotation

GitHub Actions / lint

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
return false
}
crodriguezvega marked this conversation as resolved.
Show resolved Hide resolved
}
})

keys := make([]string, 0, len(jsonObject))
for k := range jsonObject {
keys = append(keys, k)
}
sort.Strings(keys)

if len(jsonObject) != 0 {
return errorsmod.Wrapf(ErrInvalidAuthorization, "not allowed packet data keys: %s", keys)
if isMemoAllowed {
return nil
} else {

Check failure on line 186 in modules/apps/transfer/types/transfer_authorization.go

View workflow job for this annotation

GitHub Actions / lint

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
return errorsmod.Wrapf(ErrInvalidAuthorization, "not allowed memo: %s", memo)
}
crodriguezvega marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

// UnboundedSpendLimit returns the sentinel value that can be used
Expand Down
28 changes: 20 additions & 8 deletions modules/apps/transfer/types/transfer_authorization_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package types_test

import (
"fmt"

sdkmath "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -11,7 +13,10 @@ import (
"github.com/cosmos/ibc-go/v8/testing/mock"
)

const testMemo = `{"wasm":{"contract":"osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg","msg":{"osmosis_swap":{"output_denom":"uosmo","slippage":{"twap":{"slippage_percentage":"20","window_seconds":10}},"receiver":"feeabs/feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw","on_failed_delivery":"do_nothing"}}}}`
const (
testMemo1 = `{"wasm":{"contract":"osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg","msg":{"osmosis_swap":{"output_denom":"uosmo","slippage":{"twap":{"slippage_percentage":"20","window_seconds":10}},"receiver":"feeabs/feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw","on_failed_delivery":"do_nothing"}}}}`
testMemo2 = `{"forward":{"channel":"channel-11","port":"transfer","receiver":"stars1twfv52yxcyykx2lcvgl42svw46hsm5dd4ww6xy","retries":2,"timeout":1712146014542131200}}`
)

func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
var (
Expand Down Expand Up @@ -122,7 +127,7 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
func() {
allowedList := []string{"*"}
transferAuthz.Allocations[0].AllowedPacketData = allowedList
msgTransfer.Memo = testMemo
msgTransfer.Memo = testMemo1
},
func(res authz.AcceptResponse, err error) {
suite.Require().NoError(err)
Expand All @@ -135,9 +140,9 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
{
"success: transfer memo allowed",
func() {
allowedList := []string{"wasm", "forward"}
allowedList := []string{testMemo1, testMemo2}
transferAuthz.Allocations[0].AllowedPacketData = allowedList
msgTransfer.Memo = testMemo
msgTransfer.Memo = testMemo1
},
func(res authz.AcceptResponse, err error) {
suite.Require().NoError(err)
Expand All @@ -152,7 +157,7 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
func() {
allowedList := []string{}
transferAuthz.Allocations[0].AllowedPacketData = allowedList
msgTransfer.Memo = testMemo
msgTransfer.Memo = testMemo1
},
func(res authz.AcceptResponse, err error) {
suite.Require().Error(err)
Expand All @@ -161,13 +166,13 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
{
"memo not allowed",
func() {
allowedList := []string{"forward"}
allowedList := []string{testMemo1}
transferAuthz.Allocations[0].AllowedPacketData = allowedList
msgTransfer.Memo = testMemo
msgTransfer.Memo = testMemo2
},
func(res authz.AcceptResponse, err error) {
suite.Require().Error(err)
suite.Require().ErrorContains(err, "not allowed packet data keys: [wasm]")
suite.Require().ErrorContains(err, fmt.Sprintf("not allowed memo: %s", testMemo2))
},
},
{
Expand Down Expand Up @@ -310,6 +315,13 @@ func (suite *TypesTestSuite) TestTransferAuthorizationValidateBasic() {
},
true,
},
{
"success: wildcard allowed packet data",
func() {
transferAuthz.Allocations[0].AllowedPacketData = []string{"*"}
},
true,
},
{
"empty allocations",
func() {
Expand Down
4 changes: 2 additions & 2 deletions proto/ibc/applications/transfer/v1/authz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ message Allocation {
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// allow list of receivers, an empty allow list permits any receiver address
repeated string allow_list = 4;
// allow list of packet data keys, an empty list prohibits all packet data keys;
// a list only with "*" permits any packet data key
// allow list of memo strings, an empty list prohibits all memo strings;
// a list only with "*" permits any memo string
repeated string allowed_packet_data = 5;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe in v9 we can rename this field?

}

Expand Down
Loading