Skip to content

Commit 94221f7

Browse files
mergify[bot]Vvaradinovdamiannolan
authored
imp: represent unlimited approvals with MaxUint256 value (backport #3454) (#3580)
* 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 # modules/apps/transfer/types/transfer_authorization_test.go * resolving conflicts --------- Co-authored-by: Vladislav Varadinov <vladislav.varadinov@gmail.com> Co-authored-by: Damian Nolan <damiannolan@gmail.com>
1 parent 55caad4 commit 94221f7

File tree

3 files changed

+101
-27
lines changed

3 files changed

+101
-27
lines changed

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

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

33
import (
4+
"math/big"
5+
6+
errorsmod "cosmossdk.io/errors"
7+
sdkmath "cosmossdk.io/math"
8+
49
sdk "github.com/cosmos/cosmos-sdk/types"
510
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
611
"github.com/cosmos/cosmos-sdk/x/authz"
@@ -9,10 +14,11 @@ import (
914
host "github.com/cosmos/ibc-go/v6/modules/core/24-host"
1015
)
1116

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

19+
// maxUint256 is the maximum value for a 256 bit unsigned integer.
20+
var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
21+
1622
// NewTransferAuthorization creates a new TransferAuthorization object.
1723
func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization {
1824
return &TransferAuthorization{
@@ -33,37 +39,45 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep
3339
}
3440

3541
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-
}
42+
if !(allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort) {
43+
continue
44+
}
4145

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

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-
}
50+
// If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit.
51+
if allocation.SpendLimit.AmountOf(msgTransfer.Token.Denom).Equal(UnboundedSpendLimit()) {
52+
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &a}, nil
53+
}
54+
55+
limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token)
56+
if isNegative {
57+
return authz.AcceptResponse{}, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit")
58+
}
6159

60+
if limitLeft.IsZero() {
61+
a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...)
62+
if len(a.Allocations) == 0 {
63+
return authz.AcceptResponse{Accept: true, Delete: true}, nil
64+
}
6265
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
6366
Allocations: a.Allocations,
6467
}}, nil
6568
}
69+
a.Allocations[index] = Allocation{
70+
SourcePort: allocation.SourcePort,
71+
SourceChannel: allocation.SourceChannel,
72+
SpendLimit: limitLeft,
73+
AllowList: allocation.AllowList,
74+
}
75+
76+
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
77+
Allocations: a.Allocations,
78+
}}, nil
6679
}
80+
6781
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist")
6882
}
6983

@@ -117,6 +131,8 @@ func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) b
117131
return true
118132
}
119133

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

modules/apps/transfer/types/transfer_authorization_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() {
8686
suite.Require().Len(updatedAuthz.Allocations, 1)
8787
},
8888
},
89+
{
90+
"success: with unlimited spend limit of max uint256",
91+
func() {
92+
transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit()))
93+
},
94+
func(res authz.AcceptResponse, err error) {
95+
suite.Require().NoError(err)
96+
97+
updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization)
98+
suite.Require().True(ok)
99+
100+
remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom)
101+
suite.Require().True(types.UnboundedSpendLimit().Equal(remainder))
102+
},
103+
},
104+
{
105+
"test multiple coins does not overspend",
106+
func() {
107+
transferAuthz.Allocations[0].SpendLimit = transferAuthz.Allocations[0].SpendLimit.Add(
108+
sdk.NewCoins(
109+
sdk.NewCoin("test-denom", sdk.NewInt(100)),
110+
sdk.NewCoin("test-denom2", sdk.NewInt(100)),
111+
)...,
112+
)
113+
msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(50))
114+
},
115+
func(res authz.AcceptResponse, err error) {
116+
suite.Require().NoError(err)
117+
118+
updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization)
119+
suite.Require().True(ok)
120+
121+
remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom)
122+
suite.Require().True(sdk.NewInt(50).Equal(remainder))
123+
124+
remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom")
125+
suite.Require().True(sdk.NewInt(100).Equal(remainder))
126+
127+
remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom2")
128+
suite.Require().True(sdk.NewInt(100).Equal(remainder))
129+
},
130+
},
89131
{
90132
"no spend limit set for MsgTransfer port/channel",
91133
func() {
@@ -190,6 +232,13 @@ func (suite *TypesTestSuite) TestTransferAuthorizationValidateBasic() {
190232
},
191233
true,
192234
},
235+
{
236+
"success: with unlimited spend limit of max uint256",
237+
func() {
238+
transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit()))
239+
},
240+
true,
241+
},
193242
{
194243
"empty allocations",
195244
func() {

0 commit comments

Comments
 (0)