Skip to content

Commit 1a99cb7

Browse files
mergify[bot]Vvaradinovdamiannolan
authored
imp: represent unlimited approvals with MaxUint256 value (backport #3454) (#3581)
* imp: represent unlimited approvals with MaxUint256 value (#3454) * imp: represent unlimited approvals with a nil value * CHANGELOG * Update CHANGELOG.md * fix: updated unlimited spending limit to be represented with the MaxInt64 * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * fix: lint * Update modules/apps/transfer/types/transfer_authorization.go * fix: update failing test and add test case suggested in review * fix: moved isAllowedAddress check before coin loop * fix: use maxUint256 case so it aligns with what's being passed from the EVM extension * refactor transfer authz to remove coins iteration in favour of explicit amount checking * make format * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Damian Nolan <damiannolan@gmail.com> * fix: add the Authorization to Updated. * moving allowlist check to before spend limit logic * Apply suggestions from code review * fix: add comment to spend limit check * review feedback * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Damian Nolan <damiannolan@gmail.com> * Update docs/apps/transfer/authorizations.md * fix: changed to new sentinel value name --------- Co-authored-by: Carlos Rodriguez <carlos@interchain.io> Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Damian Nolan <damiannolan@gmail.com> (cherry picked from commit 7e6eb4c) # Conflicts: # CHANGELOG.md # e2e/tests/core/client_test.go # e2e/testsuite/grpc_query.go # modules/apps/transfer/types/transfer_authorization.go * resolving conflicts --------- Co-authored-by: Vladislav Varadinov <vladislav.varadinov@gmail.com> Co-authored-by: Damian Nolan <damiannolan@gmail.com>
1 parent eec18b7 commit 1a99cb7

File tree

4 files changed

+102
-28
lines changed

4 files changed

+102
-28
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
4646

4747
### Improvements
4848

49+
* (apps/transfer) [\#3454](https://github.com/cosmos/ibc-go/pull/3454) Support transfer authorization unlimited spending when the max `uint256` value is provided as limit.
50+
4951
### Features
5052

5153
### Bug Fixes

docs/apps/transfer/authorizations.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `TransferAuthorization`
22

3-
`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit MsgTransfer on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module.
3+
`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module.
44

55
More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel.
66

@@ -13,7 +13,7 @@ It takes:
1313

1414
- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred.
1515

16-
- a `SpendLimit` that specifies the maximum amount of tokens the grantee can spend. The `SpendLimit` is updated as the tokens are spent. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes.
16+
- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transfered, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes.
1717

1818
- 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`.
1919

modules/apps/transfer/types/transfer_authorization.go

+49-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package types
22

33
import (
4+
"math/big"
5+
6+
errorsmod "cosmossdk.io/errors"
7+
sdkmath "cosmossdk.io/math"
48
sdk "github.com/cosmos/cosmos-sdk/types"
59
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
610
"github.com/cosmos/cosmos-sdk/x/authz"
@@ -9,10 +13,11 @@ import (
913
host "github.com/cosmos/ibc-go/v7/modules/core/24-host"
1014
)
1115

12-
const gasCostPerIteration = uint64(10)
13-
1416
var _ authz.Authorization = &TransferAuthorization{}
1517

18+
// maxUint256 is the maximum value for a 256 bit unsigned integer.
19+
var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
20+
1621
// NewTransferAuthorization creates a new TransferAuthorization object.
1722
func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization {
1823
return &TransferAuthorization{
@@ -33,37 +38,45 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep
3338
}
3439

3540
for index, allocation := range a.Allocations {
36-
if allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort {
37-
limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token)
38-
if isNegative {
39-
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit")
40-
}
41+
if !(allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort) {
42+
continue
43+
}
4144

42-
if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) {
43-
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "not allowed address for transfer")
44-
}
45+
if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) {
46+
return authz.AcceptResponse{}, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "not allowed receiver address for transfer")
47+
}
4548

46-
if limitLeft.IsZero() {
47-
a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...)
48-
if len(a.Allocations) == 0 {
49-
return authz.AcceptResponse{Accept: true, Delete: true}, nil
50-
}
51-
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
52-
Allocations: a.Allocations,
53-
}}, nil
54-
}
55-
a.Allocations[index] = Allocation{
56-
SourcePort: allocation.SourcePort,
57-
SourceChannel: allocation.SourceChannel,
58-
SpendLimit: limitLeft,
59-
AllowList: allocation.AllowList,
60-
}
49+
// If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit.
50+
if allocation.SpendLimit.AmountOf(msgTransfer.Token.Denom).Equal(UnboundedSpendLimit()) {
51+
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &a}, nil
52+
}
6153

54+
limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token)
55+
if isNegative {
56+
return authz.AcceptResponse{}, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit")
57+
}
58+
59+
if limitLeft.IsZero() {
60+
a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...)
61+
if len(a.Allocations) == 0 {
62+
return authz.AcceptResponse{Accept: true, Delete: true}, nil
63+
}
6264
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
6365
Allocations: a.Allocations,
6466
}}, nil
6567
}
68+
a.Allocations[index] = Allocation{
69+
SourcePort: allocation.SourcePort,
70+
SourceChannel: allocation.SourceChannel,
71+
SpendLimit: limitLeft,
72+
AllowList: allocation.AllowList,
73+
}
74+
75+
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
76+
Allocations: a.Allocations,
77+
}}, nil
6678
}
79+
6780
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist")
6881
}
6982

@@ -117,6 +130,8 @@ func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) b
117130
return true
118131
}
119132

133+
gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat
134+
120135
for _, addr := range allowedAddrs {
121136
ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization")
122137
if addr == receiver {
@@ -125,3 +140,12 @@ func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) b
125140
}
126141
return false
127142
}
143+
144+
// UnboundedSpendLimit returns the sentinel value that can be used
145+
// as the amount for a denomination's spend limit for which spend limit updating
146+
// should be disabled. Please note that using this sentinel value means that a grantee
147+
// will be granted the privilege to do ICS20 token transfers for the total amount
148+
// of the denomination available at the granter's account.
149+
func UnboundedSpendLimit() sdkmath.Int {
150+
return sdk.NewIntFromBigInt(maxUint256)
151+
}

modules/apps/transfer/types/transfer_authorization_test.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package types_test
33
import (
44
sdk "github.com/cosmos/cosmos-sdk/types"
55
"github.com/cosmos/cosmos-sdk/x/authz"
6-
76
"github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
87
ibctesting "github.com/cosmos/ibc-go/v7/testing"
98
"github.com/cosmos/ibc-go/v7/testing/mock"
@@ -86,6 +85,48 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
8685
suite.Require().Len(updatedAuthz.Allocations, 1)
8786
},
8887
},
88+
{
89+
"success: with unlimited spend limit of max uint256",
90+
func() {
91+
transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit()))
92+
},
93+
func(res authz.AcceptResponse, err error) {
94+
suite.Require().NoError(err)
95+
96+
updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization)
97+
suite.Require().True(ok)
98+
99+
remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom)
100+
suite.Require().True(types.UnboundedSpendLimit().Equal(remainder))
101+
},
102+
},
103+
{
104+
"test multiple coins does not overspend",
105+
func() {
106+
transferAuthz.Allocations[0].SpendLimit = transferAuthz.Allocations[0].SpendLimit.Add(
107+
sdk.NewCoins(
108+
sdk.NewCoin("test-denom", sdk.NewInt(100)),
109+
sdk.NewCoin("test-denom2", sdk.NewInt(100)),
110+
)...,
111+
)
112+
msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(50))
113+
},
114+
func(res authz.AcceptResponse, err error) {
115+
suite.Require().NoError(err)
116+
117+
updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization)
118+
suite.Require().True(ok)
119+
120+
remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom)
121+
suite.Require().True(sdk.NewInt(50).Equal(remainder))
122+
123+
remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom")
124+
suite.Require().True(sdk.NewInt(100).Equal(remainder))
125+
126+
remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom2")
127+
suite.Require().True(sdk.NewInt(100).Equal(remainder))
128+
},
129+
},
89130
{
90131
"no spend limit set for MsgTransfer port/channel",
91132
func() {
@@ -190,6 +231,13 @@ func (suite *TypesTestSuite) TestTransferAuthorizationValidateBasic() {
190231
},
191232
true,
192233
},
234+
{
235+
"success: with unlimited spend limit of max uint256",
236+
func() {
237+
transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit()))
238+
},
239+
true,
240+
},
193241
{
194242
"empty allocations",
195243
func() {

0 commit comments

Comments
 (0)