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 all 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
3,261 changes: 2,819 additions & 442 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.

23 changes: 23 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,17 @@ 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;
repeated string allowed_proposal_types = 5;
}
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, options.GovKeeper),
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
7 changes: 6 additions & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//go:build app_v1
//go:build !app_v1

// TODO: (revert) added for testing

package simapp

Expand Down Expand Up @@ -522,6 +524,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,10 +569,12 @@ 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,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
GovKeeper: app.GovKeeper,
},
&app.CircuitKeeper,
},
Expand Down
5 changes: 4 additions & 1 deletion simapp/app_v2.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//go:build !app_v1
//go:build app_v1

// TODO: (revert) add for testing

package simapp

Expand Down Expand Up @@ -89,6 +91,7 @@ type SimApp struct {
}

func init() {

userHomeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
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
3 changes: 3 additions & 0 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ 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
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params) error
TxFeeChecker TxFeeChecker
GovKeeper GovKeeper
}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
Expand All @@ -46,6 +48,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
NewValidateMemoDecorator(options.AccountKeeper),
NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
NewAuthzDecorator(options.AuthzKeeper, options.AccountKeeper, options.GovKeeper),
NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
NewValidateSigCountDecorator(options.AccountKeeper),
NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
Expand Down
228 changes: 228 additions & 0 deletions x/auth/ante/authz_rules_ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package ante

import (
"fmt"
"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"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
stakingv1beta1 "github.com/cosmos/cosmos-sdk/x/staking/types"

errorsmod "cosmossdk.io/errors"
)

type AuthzDecorator struct {
azk AuthzKeeper
ak AccountKeeper
govKeeper GovKeeper
}

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

// AnteHandle 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) {
// Ensure the transaction can be verified for signatures
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
}

// Get the signers of the transaction
signers, err := sigTx.GetSigners()
if err != nil {
return ctx, err
}

// Assume the first signer is the grantee
grantee := signers[0]

// Get the messages in the transaction
msgs := tx.GetMsgs()
for _, msg := range msgs {
// Check if the message is an authorization message
authzMsg, ok := msg.(*authztypes.MsgExec)
if !ok {
continue
}

// Get the inner messages of the authorization message
authzMsgs, err := authzMsg.GetMessages()
if err != nil {
return ctx, err
}

// Handle each inner message based on its type
for _, innerMsg := range authzMsgs {
switch innerMsg1 := innerMsg.(type) {
case *banktypes.MsgSend:
if err := azd.handleSendAuthzRules(ctx, innerMsg1, grantee); err != nil {
return ctx, err
}
case *stakingv1beta1.MsgDelegate:
if err := azd.handleStakeAuthzRules(ctx, innerMsg1, grantee); err != nil {
return ctx, err
}
case *govv1.MsgVote:
if err := azd.handleProposalAuthzRules(ctx, innerMsg1, grantee); err != nil {
return ctx, err
}

default:
fmt.Printf("Unhandled inner message type: %T\n", innerMsg)
}
}
}

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

// handleSendAuthzRules checks if a MsgSend transaction is authorized based on the rules set by the granter.
func (azd AuthzDecorator) handleSendAuthzRules(ctx sdk.Context, msg *banktypes.MsgSend, grantee []byte) error {
// Convert the sender's address to bytes
granter, err := azd.ak.AddressCodec().StringToBytes(msg.FromAddress)
if err != nil {
return err
}

// Retrieve authorization rules
_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&banktypes.MsgSend{}))

// Initialize maps for quick lookup
allowedRecipients := make(map[string]struct{})
var maxAmount sdk.Coins

// Populate maps with rule values
for _, rule := range rules {
switch rule.Key {
case authztypes.AllowedRecipients:
for _, recipient := range rule.Values {
allowedRecipients[recipient] = struct{}{}
}
case authztypes.MaxAmount:
maxAmount, err = sdk.ParseCoinsNormalized(strings.Join(rule.Values, ","))
if err != nil {
return err
}
}
}

// Check if recipient is allowed
if len(allowedRecipients) > 0 {
if _, isAllowed := allowedRecipients[msg.ToAddress]; !isAllowed {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Recipient is not in the allowed list of the grant")
}
}

// Check if the amount does not exceed the maximum allowed
if maxAmount != nil {
if !maxAmount.IsAllGTE(msg.Amount) {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}
}

return nil
}

// handleStakeAuthzRules checks if a MsgDelegate transaction is authorized based on the rules set by the granter.
func (azd AuthzDecorator) handleStakeAuthzRules(ctx sdk.Context, msg *stakingv1beta1.MsgDelegate, grantee []byte) error {
// Convert the delegator's address to bytes
granter, err := azd.ak.AddressCodec().StringToBytes(msg.DelegatorAddress)
if err != nil {
return err
}

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

// Initialize maps for quick lookup
allowedValidators := make(map[string]struct{})
var maxStakeAmount sdk.Coins

// Populate maps with rule values
for _, rule := range rules {
switch rule.Key {
case authztypes.AllowedStakeValidators:
for _, validator := range rule.Values {
allowedValidators[validator] = struct{}{}
}
case authztypes.AllowedMaxStakeAmount:
maxStakeAmount, err = sdk.ParseCoinsNormalized(strings.Join(rule.Values, ","))
if err != nil {
return err
}
}
}

// Check if validator is allowed
if len(allowedValidators) > 0 {
if _, isAllowed := allowedValidators[msg.ValidatorAddress]; !isAllowed {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Validator is not in the allowed validators of the grant")
}
}

// Check if the stake amount does not exceed the maximum allowed
if maxStakeAmount != nil {
amount, err := sdk.ParseCoinNormalized(msg.Amount.String())
if err != nil {
return err
}
if !maxStakeAmount.IsAllGTE(sdk.NewCoins(amount)) {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}
}

return nil
}

// handleProposalAuthzRules checks if a MsgVote transaction is authorized based on the rules set by the granter.
func (azd AuthzDecorator) handleProposalAuthzRules(ctx sdk.Context, msg *govv1.MsgVote, grantee []byte) error {
// Convert the voter's address to bytes
granter, err := azd.ak.AddressCodec().StringToBytes(msg.Voter)
if err != nil {
return err
}

// Retrieve the proposal by ID
proposal, err := azd.govKeeper.GetProposalById(ctx, msg.ProposalId)
if err != nil {
return err
}

// Retrieve authorization rules
_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&govv1.MsgVote{}))
if len(rules) == 0 {
return nil
}

// Initialize a map for quick lookup of allowed proposal types
allowedProposalTypes := make(map[string]struct{})

// Populate map with rule values
for _, rule := range rules {
if rule.Key == authztypes.AllowedProposalTypes {
for _, allowedProposalType := range rule.Values {
allowedProposalTypes[allowedProposalType] = struct{}{}
}
}
}

// Check if any of the proposal messages' types are allowed
for _, msg := range proposal.GetMessages() {
if _, exists := allowedProposalTypes[msg.GetTypeUrl()]; exists {
return nil // Proposal type is allowed
}
}

return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Voter is not allowed to vote on the proposal")
}
Loading
Loading