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

feat: authz rules grant specific #3

Draft
wants to merge 30 commits into
base: vitwit/v0.50.5
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 26 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
2,978 changes: 2,610 additions & 368 deletions api/cosmos/authz/v1beta1/authz.pulsar.go

Large diffs are not rendered by default.

200 changes: 138 additions & 62 deletions api/cosmos/authz/v1beta1/tx.pulsar.go

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions proto/cosmos/authz/v1beta1/authz.proto
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ message Grant {
// doesn't have a time expiration (other conditions in `authorization`
// may apply to invalidate the grant)
google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true];

// rules are conditions to execute the grant.
repeated Rule rules = 3;
}

// rules are conditions to execute the grant.
message Rule {
string key = 1;
repeated string values = 2;
}

// GrantAuthorization extends a grant with both the addresses of the grantee and granter.
Expand All @@ -46,3 +55,16 @@ message GrantQueueItem {
// msg_type_urls contains the list of TypeURL of a sdk.Msg.
repeated string msg_type_urls = 1;
}

// AllowedGrantRulesKeys contains the keys allowed for each message.
message AllowedGrantRulesKeys {
repeated cosmos.authz.v1beta1.Rule keys = 1;
}

// AppAuthzRules is rules passed to the authz app.
message AppAuthzRules {
repeated string allowed_recipients = 1;
repeated string max_amount = 2;
repeated string allowed_stake_validators = 3;
repeated string allowed_max_stake_amount = 4;
}
3 changes: 3 additions & 0 deletions proto/cosmos/authz/v1beta1/tx.proto
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ message MsgGrant {
string grantee = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];

cosmos.authz.v1beta1.Grant grant = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// rules are conditions to execute the grant.
bytes rules = 4;
}
atheeshp marked this conversation as resolved.
Show resolved Hide resolved

// MsgGrantResponse defines the Msg/MsgGrant response type.
Expand Down
Empty file modified scripts/README.md
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions simapp/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
ante.NewAuthzDecorator(options.AuthzKeeper, options.AccountKeeper),
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
Expand Down
2 changes: 2 additions & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ func NewSimApp(
app.SetPreBlocker(app.PreBlocker)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)

app.setAnteHandler(txConfig)

// In v0.46, the SDK introduces _postHandlers_. PostHandlers are like
Expand Down Expand Up @@ -566,6 +567,7 @@ func (app *SimApp) setAnteHandler(txConfig client.TxConfig) {
HandlerOptions{
ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
AuthzKeeper: app.AuthzKeeper,
BankKeeper: app.BankKeeper,
SignModeHandler: txConfig.SignModeHandler(),
FeegrantKeeper: app.FeeGrantKeeper,
Expand Down
24 changes: 24 additions & 0 deletions simapp/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
circuittypes "cosmossdk.io/x/circuit/types"
upgradetypes "cosmossdk.io/x/upgrade/types"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// UpgradeName defines the on-chain upgrade name for the sample SimApp upgrade
Expand All @@ -26,6 +30,26 @@
},
)

app.UpgradeKeeper.SetUpgradeHandler(
"v2",
func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
app.AuthzKeeper.SetAuthzRulesKeys(ctx, &authz.AllowedGrantRulesKeys{
Keys: []*authz.Rule{
{
Key: sdk.MsgTypeURL(&banktypes.MsgSend{}),
Values: []string{authz.MaxAmount, authz.AllowedRecipients},
},
{
Key: sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}),
Values: []string{authz.AllowedStakeValidators, authz.AllowedMaxStakeAmount},
},
},
})
Comment on lines +36 to +47

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.

return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM)
},
)

upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(err)
Expand Down
1 change: 1 addition & 0 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// HandlerOptions are the options required for constructing a default SDK AnteHandler.
type HandlerOptions struct {
AccountKeeper AccountKeeper
AuthzKeeper AuthzKeeper
BankKeeper types.BankKeeper
ExtensionOptionChecker ExtensionOptionChecker
FeegrantKeeper FeegrantKeeper
Expand Down
151 changes: 151 additions & 0 deletions x/auth/ante/authz_rules_ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package ante

import (
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
authztypes "github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

stakingv1beta1 "cosmossdk.io/api/cosmos/staking/v1beta1"
errorsmod "cosmossdk.io/errors"
)

type AuthzDecorator struct {
azk AuthzKeeper
ak AccountKeeper
}

func NewAuthzDecorator(azk AuthzKeeper, ak AccountKeeper) AuthzDecorator {
return AuthzDecorator{
azk: azk,
ak: ak,
}
}

// AuthzDecorator checks the authorization message grants for some rules.
func (azd AuthzDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
}

signers, err := sigTx.GetSigners()
if err != nil {
return ctx, err
}

grantee := signers[0]

msgs := tx.GetMsgs()
for _, msg := range msgs {
// Check if the message is an authorization message
if authzMsg, ok := msg.(*authztypes.MsgExec); ok {

authzMsgs, err := authzMsg.GetMessages()
if err != nil {
return ctx, err
}

for _, innerMsg := range authzMsgs {
switch innerMsgConverted := innerMsg.(type) {
case *banktypes.MsgSend:
err := azd.handleSendAuthzRules(ctx, innerMsgConverted, grantee)
if err != nil {
return ctx, err
}
case *stakingv1beta1.MsgDelegate:
err := azd.handleStakeAuthzRules(ctx, innerMsgConverted, grantee)
if err != nil {
return ctx, err
}
}
}
}
}

// Continue with the transaction if all checks pass
return next(ctx, tx, simulate)
}

// handleCheckSendAuthzRules returns true if the rules are voilated
func (azd AuthzDecorator) handleSendAuthzRules(ctx sdk.Context, msg *banktypes.MsgSend, grantee []byte) error {
granter, err := azd.ak.AddressCodec().StringToBytes(msg.FromAddress)
if err != nil {
return err
}

_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&banktypes.MsgSend{}))
for _, rule := range rules {
if rule.Key == authztypes.AllowedRecipients {
isAllowed := false
for _, allowedRecipient := range rule.Values {
if msg.ToAddress == allowedRecipient {
isAllowed = true
break
}
}

if !isAllowed {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Recipient is not in the allowed list of the grant")
}
}

if rule.Key == authztypes.MaxAmount {
limit, err := sdk.ParseCoinsNormalized(strings.Join(rule.Values, ","))
if err != nil {
return err
}
if !limit.IsAllGTE(msg.Amount) {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}
}

}

return nil
}

func (azd AuthzDecorator) handleStakeAuthzRules(ctx sdk.Context, msg *stakingv1beta1.MsgDelegate, grantee []byte) error {
granter, err := azd.ak.AddressCodec().StringToBytes(msg.DelegatorAddress)
if err != nil {
return err
}

_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&stakingv1beta1.MsgDelegate{}))

for _, rule := range rules {
if rule.Key == authztypes.AllowedStakeValidators {
isAllowed := false
for _, allowedValidator := range rule.Values {
if msg.ValidatorAddress == allowedValidator {
isAllowed = true
break
}
}

if !isAllowed {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Validator is not in the allowed validators of the grant")
}
}

if rule.Key == authztypes.AllowedMaxStakeAmount {
limit, err := sdk.ParseCoinsNormalized(strings.Join(rule.Values, ","))
if err != nil {
return err
}
amount, err := sdk.ParseCoinNormalized(msg.Amount.String())
if err != nil {
return err
}

if !limit.IsAllGTE(sdk.NewCoins(amount)) {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}
}
}

return nil
}
65 changes: 65 additions & 0 deletions x/auth/ante/authz_rules_spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Description

Problem:
The existing authorization (authz) module lacks the flexibility to grant permissions (authz grants) for various types of messages along with specific conditions or rules. This limitation constrains users from customizing their transaction behavior based on specific needs or strategies.
Copy link

Choose a reason for hiding this comment

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

Suggested change
The existing authorization (authz) module lacks the flexibility to grant permissions (authz grants) for various types of messages along with specific conditions or rules. This limitation constrains users from customizing their transaction behavior based on specific needs or strategies.
The current authorization (authz) module lacks the flexibility needed to grant permissions (authz grants) for various types of messages along with specific conditions or rules. This limitation prevents users from customizing their transaction behavior according to specific needs or strategies.
To address this issue, we propose enhancing the authz module to support more granular permissions and conditional rules, allowing for greater customization and control over transaction authorization.


## Specific Examples of Limitations:
Swapping Reward Tokens:
- Currently, users cannot set a rule to swap their reward tokens or any other tokens for another token with a specified limit.
Copy link

Choose a reason for hiding this comment

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

Suggested change
Swapping Reward Tokens:
- Currently, users cannot set a rule to swap their reward tokens or any other tokens for another token with a specified limit.
Managing Reward Tokens:
At present, users are able to restake their tokens via authz. But it can do more. Currently users are unable to establish rules for swapping their reward tokens as a strategy as it requires IBCTransfer or PacketForward msgs access. It's not secure to give this grant currently as the recipient address can be anything and grantee can behave maliciously. But if there's a way to restrict recipient address to match with granter's address, this problem is solved. This functionality is necessary to enable users to automate and customize their token management strategies effectively.

Sending Tokens to Selected Addresses:
- Users are unable to authorize sending tokens to a pre-defined or selected address, restricting the ability to control where tokens are transferred.
Copy link

Choose a reason for hiding this comment

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

Suggested change
- Users are unable to authorize sending tokens to a pre-defined or selected address, restricting the ability to control where tokens are transferred.
- Users currently cannot authorize sending tokens to a pre-defined or selected address. This restriction limits their ability to control and automate the transfer of tokens to specific recipients, thereby reducing the efficiency and flexibility of their token management strategies. For example, if an organization wants to authorize an accountant to process salaries every month, the current system's limitations prevent this. Implementing an authz grant to recurrently allow a user to send a specified amount to certain accounts would solve this issue. This feature would automate salary payments, ensuring timely and accurate transactions while reducing administrative overhead.

Staking Tokens with Limitations:
Copy link

Choose a reason for hiding this comment

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

replace it with gov usecase..

Copy link

Choose a reason for hiding this comment

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

also, consider spec format of cosmos-sdk. It describes the problem, but clearly explain about solution, conclusion etc instead of PR diff.

- The module does not allow users to grant permission to stake tokens with certain limits or to stake only with selected validators. This limits the user's control over staking decisions.

## PR diff:
This PR adds a feature which an authz can be granted with some rules,
for example:
- if a staker wants to stake some portion of rewards he can do that by allowing max stake amount
- if he wants to stake only to selected validators
- swap some portion of rewards to another token or liquid staked token
- also we can add rules to every message before granting.

Changes:
updated the ante handlers flow to check in the message is executing the authz message and any rules need to be checked before processing the message. if the message is not reaching the rules then it will eventually fail.

added an extra ante handler:
```
// handleCheckSendAuthzRules returns true if the rules are voilated
func (azd AuthzDecorator) handleSendAuthzRules(ctx sdk.Context, msg *banktypes.MsgSend, grantee []byte) error {
granter, err := azd.ak.AddressCodec().StringToBytes(msg.FromAddress)
if err != nil {
return err
}

_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&banktypes.MsgSend{}))
for _, rule := range rules {
if rule.Key == authztypes.AllowedRecipients {
isAllowed := false
for _, allowedRecipient := range rule.Values {
if msg.ToAddress == allowedRecipient {
isAllowed = true
break
}
}

if !isAllowed {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Recipient is not in the allowed list of the grant")
}
}

if rule.Key == authztypes.MaxAmount {
limit, err := sdk.ParseCoinsNormalized(strings.Join(rule.Values, ","))
if err != nil {
return err
}
if !limit.IsAllGTE(msg.Amount) {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}
}

}

return nil
}
```
the above snippet checks the rules for `MsgSend` likewise we can add checks to every messages.
5 changes: 5 additions & 0 deletions x/auth/ante/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
)

// AccountKeeper defines the contract needed for AccountKeeper related APIs.
Expand All @@ -23,3 +24,7 @@ type AccountKeeper interface {
type FeegrantKeeper interface {
UseGrantedFees(ctx context.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error
}

type AuthzKeeper interface {
GetAuthzWithRules(ctx context.Context, grantee, granter sdk.AccAddress, msgType string) (authz.Authorization, []*authz.Rule)
}
3 changes: 2 additions & 1 deletion x/authz/authorization_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// NewGrant returns new Grant. Expiration is optional and noop if null.
// It returns an error if the expiration is before the current block time,
// which is passed into the `blockTime` arg.
func NewGrant(blockTime time.Time, a Authorization, expiration *time.Time) (Grant, error) {
func NewGrant(blockTime time.Time, a Authorization, expiration *time.Time, rules []*Rule) (Grant, error) {
if expiration != nil && !expiration.After(blockTime) {
return Grant{}, errorsmod.Wrapf(ErrInvalidExpirationTime, "expiration must be after the current block time (%v), got %v", blockTime.Format(time.RFC3339), expiration.Format(time.RFC3339))
}
Expand All @@ -29,6 +29,7 @@ func NewGrant(blockTime time.Time, a Authorization, expiration *time.Time) (Gran
return Grant{
Expiration: expiration,
Authorization: any,
Rules: rules,
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion x/authz/authorization_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestNewGrant(t *testing.T) {
for _, tc := range tcs {
tc := tc
t.Run(tc.title, func(t *testing.T) {
_, err := NewGrant(tc.blockTime, tc.a, tc.expire)
_, err := NewGrant(tc.blockTime, tc.a, tc.expire, nil)
expecError(require.New(t), tc.err, err)
})
}
Expand Down
Loading
Loading