-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
x/authz charge gas for expensive authorizations #8995
Changes from 19 commits
42f2949
5b616c1
6d40f03
730121e
e7a3636
be5c12f
bdfd3ee
f40136b
7a2b815
ea7222f
0b17b65
733ef55
d706b8e
82aed77
71900e7
75b4ef8
5f75011
f497bcc
71b7093
b1eebfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package types_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/x/authz/types" | ||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGenericAuthorization(t *testing.T) { | ||
t.Log("verify ValidateBasic returns error for non-service msg") | ||
authorization := types.NewGenericAuthorization(banktypes.TypeMsgSend) | ||
require.Error(t, authorization.ValidateBasic()) | ||
|
||
t.Log("verify ValidateBasic returns nil for service msg") | ||
authorization = types.NewGenericAuthorization(banktypes.SendAuthorization{}.MethodName()) | ||
require.NoError(t, authorization.ValidateBasic()) | ||
require.Equal(t, banktypes.SendAuthorization{}.MethodName(), authorization.MessageName) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package types_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/simapp" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/x/bank/types" | ||
"github.com/stretchr/testify/require" | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
) | ||
|
||
var ( | ||
coins1000 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))) | ||
coins500 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(500))) | ||
fromAddr = sdk.AccAddress("_____from _____") | ||
toAddr = sdk.AccAddress("_______to________") | ||
) | ||
|
||
func TestSendAuthorization(t *testing.T) { | ||
app := simapp.Setup(false) | ||
ctx := app.BaseApp.NewContext(false, tmproto.Header{}) | ||
authorization := types.NewSendAuthorization(coins1000) | ||
|
||
t.Log("verify authorization returns valid method name") | ||
require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") | ||
require.NoError(t, authorization.ValidateBasic()) | ||
send := types.NewMsgSend(fromAddr, toAddr, coins1000) | ||
srvMsg := sdk.ServiceMsg{ | ||
MethodName: "/cosmos.bank.v1beta1.Msg/Send", | ||
Request: send, | ||
} | ||
require.NoError(t, authorization.ValidateBasic()) | ||
|
||
t.Log("verify updated authorization returns nil") | ||
updated, del, err := authorization.Accept(ctx, srvMsg) | ||
require.NoError(t, err) | ||
require.True(t, del) | ||
require.Nil(t, updated) | ||
|
||
authorization = types.NewSendAuthorization(coins1000) | ||
require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") | ||
require.NoError(t, authorization.ValidateBasic()) | ||
send = types.NewMsgSend(fromAddr, toAddr, coins500) | ||
srvMsg = sdk.ServiceMsg{ | ||
MethodName: "/cosmos.bank.v1beta1.Msg/Send", | ||
Request: send, | ||
} | ||
require.NoError(t, authorization.ValidateBasic()) | ||
updated, del, err = authorization.Accept(ctx, srvMsg) | ||
|
||
t.Log("verify updated authorization returns remaining spent limit") | ||
require.NoError(t, err) | ||
require.False(t, del) | ||
require.NotNil(t, updated) | ||
sendAuth := types.NewSendAuthorization(coins500) | ||
require.Equal(t, sendAuth.String(), updated.String()) | ||
|
||
t.Log("expect updated authorization nil after spending remaining amount") | ||
updated, del, err = updated.Accept(ctx, srvMsg) | ||
require.NoError(t, err) | ||
require.True(t, del) | ||
require.Nil(t, updated) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,21 @@ | ||
package types | ||
|
||
import ( | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
authz "github.com/cosmos/cosmos-sdk/x/authz/exported" | ||
) | ||
|
||
// 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 = &StakeAuthorization{} | ||
TypeDelegate = "/cosmos.staking.v1beta1.Msg/Delegate" | ||
TypeUndelegate = "/cosmos.staking.v1beta1.Msg/Undelegate" | ||
TypeBeginRedelegate = "/cosmos.staking.v1beta1.Msg/BeginRedelegate" | ||
_ authz.Authorization = &StakeAuthorization{} | ||
|
||
TypeDelegate = "/cosmos.staking.v1beta1.Msg/Delegate" | ||
TypeUndelegate = "/cosmos.staking.v1beta1.Msg/Undelegate" | ||
TypeBeginRedelegate = "/cosmos.staking.v1beta1.Msg/BeginRedelegate" | ||
) | ||
|
||
// NewStakeAuthorization creates a new StakeAuthorization object. | ||
|
@@ -46,8 +49,19 @@ func (authorization StakeAuthorization) MethodName() string { | |
return authzType | ||
} | ||
|
||
func (authorization StakeAuthorization) ValidateBasic() error { | ||
if authorization.MaxTokens != nil && authorization.MaxTokens.IsNegative() { | ||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "negative coin amount: %v", authorization.MaxTokens) | ||
} | ||
if authorization.AuthorizationType == AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED { | ||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unknown authorization type") | ||
} | ||
|
||
return nil | ||
aleem1314 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Accept implements Authorization.Accept. | ||
func (authorization StakeAuthorization) Accept(msg sdk.ServiceMsg, block tmproto.Header) (updated authz.Authorization, delete bool, err error) { | ||
func (authorization StakeAuthorization) Accept(ctx sdk.Context, msg sdk.ServiceMsg) (updated authz.Authorization, delete bool, err error) { | ||
var validatorAddress string | ||
var amount sdk.Coin | ||
|
||
|
@@ -68,13 +82,16 @@ func (authorization StakeAuthorization) Accept(msg sdk.ServiceMsg, block tmproto | |
isValidatorExists := false | ||
allowedList := authorization.GetAllowList().GetAddress() | ||
for _, validator := range allowedList { | ||
ctx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how do external clients know how much this number is? Are they required to dive into the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are not able to know exact number of gas. It would require a simulated run. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you possibly help with that @robert-zaremba ? |
||
if validator == validatorAddress { | ||
isValidatorExists = true | ||
break | ||
} | ||
} | ||
|
||
denyList := authorization.GetDenyList().GetAddress() | ||
for _, validator := range denyList { | ||
ctx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
if validator == validatorAddress { | ||
return nil, false, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, " cannot delegate/undelegate to %s validator", validator) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if counting of "/" in IsServiceMsg is sufficient to check if typeURL satisfies service method name type e.g. i.e. /cosmos.bank.Msg/Send