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: Allow to restrict MintCoins from app.go #10771

Merged
merged 13 commits into from
Feb 18, 2022
Merged
41 changes: 31 additions & 10 deletions x/bank/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var _ Keeper = (*BaseKeeper)(nil)
// between accounts.
type Keeper interface {
SendKeeper
WithMintCoinsRestriction(func(ctx sdk.Context, coins sdk.Coins) error) BaseKeeper

InitGenesis(sdk.Context, *types.GenesisState)
ExportGenesis(sdk.Context) *types.GenesisState
Expand Down Expand Up @@ -53,10 +54,11 @@ type Keeper interface {
type BaseKeeper struct {
BaseSendKeeper

ak types.AccountKeeper
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
paramSpace paramtypes.Subspace
ak types.AccountKeeper
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
paramSpace paramtypes.Subspace
mintCoinsRestrictionFn func(ctx sdk.Context, coins sdk.Coins) error
}

// GetPaginatedTotalSupply queries for the supply, ignoring 0 coins, with a given pagination
Expand Down Expand Up @@ -104,14 +106,28 @@ func NewBaseKeeper(
}

return BaseKeeper{
BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, paramSpace, blockedAddrs),
ak: ak,
cdc: cdc,
storeKey: storeKey,
paramSpace: paramSpace,
BaseSendKeeper: NewBaseSendKeeper(cdc, storeKey, ak, paramSpace, blockedAddrs),
ak: ak,
cdc: cdc,
storeKey: storeKey,
paramSpace: paramSpace,
mintCoinsRestrictionFn: func(ctx sdk.Context, coins sdk.Coins) error { return nil },
}
}

// WithMintCoinsRestriction restricts the bank Keeper used within a specific module to
// have restricted permissions on minting speicified denoms.
func (k BaseKeeper) WithMintCoinsRestriction(NewRestrictionFn func(ctx sdk.Context, coins sdk.Coins) error) BaseKeeper {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
k.mintCoinsRestrictionFn = func(ctx sdk.Context, coins sdk.Coins) error {
err := NewRestrictionFn(ctx, coins)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, why are we calling both old and new restriction functions?

Copy link
Contributor

Choose a reason for hiding this comment

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

We want it to nest. So I can do bankKeeper.WithMintCoinsRestriction(restriction1).WithMintCoinsRestriction(restriction2)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok the godoc should clearly reflect this, because looking at the method, that's not clear at all.

Copy link
Contributor

Choose a reason for hiding this comment

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

And I'm not even sure this is the best approach. With this approach, you don't have a clear way of reseting it. Also, you could just instead create a single restriction function that nests all of the "inner" ones.

Copy link
Contributor

@ValarDragon ValarDragon Jan 5, 2022

Choose a reason for hiding this comment

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

Why do you need to reset it though? Idea is to give a separate keeper instance to each module in its init. E.g. in Osmosis for the Gamm keepers initialization: https://github.com/osmosis-labs/osmosis/blob/main/app/keepers.go#L205-L208

I want to change that to:

	gammKeeper := gammkeeper.NewKeeper(
		appCodec, keys[gammtypes.StoreKey],
		app.GetSubspace(gammtypes.ModuleName),
		app.AccountKeeper, 
		app.BankKeeper.WithMintCoinsRestriction(NewPrefixMintCoinsRestrictionFn("gamm/pool/")), 
		app.DistrKeeper)

Copy link
Contributor

Choose a reason for hiding this comment

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

(I'm also down for a reset method if thats helpful though!)

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case you're just providing one function. Where do you nest them? I just don't think this approach is clean. But I'm not suggesting we block on it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh so an example of nesting is then for another module

app.BankKeeper.WithMintCoinsRestriction(NewBlockPrefixMintCoinsRestrictionFn("gamm/pool/"))
                           .WithMintCoinsRestriction(NewBlockPrefixMintCoinsRestrictionFn("ibc/"))

I'd realistically intend on using it such that we define standard_module_bk, that nests more restrictions as they apply.

Copy link
Contributor

@alexanderbez alexanderbez Jan 13, 2022

Choose a reason for hiding this comment

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

Yeah so in my alternative proposal (#10771 (comment)), you could easily create a function that calls both NewBlockPrefixMintCoinsRestrictionFn and NewBlockPrefixMintCoinsRestrictionFn.

func Foo() {
 if err := NewBlockPrefixMintCoinsRestrictionFn(); err !=nil {
  return err
 }

// ...
}

app.BankKeeper.WithMintCoinsRestriction(Foo)

I personally think this approach is cleaner and easier to understand. But proceed with either approach. I approved the PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@ValarDragon proposes a middleware approach, so if we have a bank keeper with default setting we make sure the restriction fn will always be called.
With @alexanderbez approach we need to remember to put include the default check function in all custom functions.

if err != nil {
return err
}
return nil
}
return k
}

// DelegateCoins performs delegation by deducting amt coins from an account with
// address addr. For vesting accounts, delegations amounts are tracked for both
// vesting and vested coins. The coins are then transferred from the delegator
Expand Down Expand Up @@ -381,6 +397,11 @@ func (k BaseKeeper) UndelegateCoinsFromModuleToAccount(
// MintCoins creates new coins from thin air and adds it to the module account.
// It will panic if the module account does not exist or is unauthorized.
func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amounts sdk.Coins) error {
err := k.mintCoinsRestrictionFn(ctx, amounts)
if err != nil {
ctx.Logger().Error("Module %s attempted minting coins %s it did not have permission for", moduleName, amounts)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
return err
}
acc := k.ak.GetModuleAccount(ctx, moduleName)
if acc == nil {
panic(sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName))
Expand All @@ -390,7 +411,7 @@ func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amounts sdk.Co
panic(sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "module account %s does not have permissions to mint tokens", moduleName))
}

err := k.addCoins(ctx, acc.GetAddress(), amounts)
err = k.addCoins(ctx, acc.GetAddress(), amounts)
if err != nil {
return err
}
Expand Down
41 changes: 41 additions & 0 deletions x/bank/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -1168,6 +1169,46 @@ func (suite *IntegrationTestSuite) getTestMetadata() []types.Metadata {
}
}

func (suite *IntegrationTestSuite) TestMintCoinRestriction() {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
maccPerms := simapp.GetMaccPerms()
maccPerms[multiPerm] = []string{authtypes.Burner, authtypes.Minter, authtypes.Staking}

suite.app.AccountKeeper = authkeeper.NewAccountKeeper(
suite.app.AppCodec(), suite.app.GetKey(authtypes.StoreKey), suite.app.GetSubspace(authtypes.ModuleName),
authtypes.ProtoBaseAccount, maccPerms, sdk.Bech32MainPrefix,
)
suite.app.AccountKeeper.SetModuleAccount(suite.ctx, multiPermAcc)

// only allow foo tokens to be minted
BankMintingRestriction := func(ctx sdk.Context, coins sdk.Coins) error {
for _, coin := range coins {
if coin.Denom != fooDenom {
return fmt.Errorf("Module %s attempted minting coins %s it did not have permission for", types.ModuleName, coin.Denom)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
}
}
return nil
}
suite.app.BankKeeper = keeper.NewBaseKeeper(suite.app.AppCodec(), suite.app.GetKey(types.StoreKey),
suite.app.AccountKeeper, suite.app.GetSubspace(types.ModuleName), nil).WithMintCoinsRestriction(BankMintingRestriction)

suite.Require().NoError(
suite.app.BankKeeper.MintCoins(
suite.ctx,
multiPermAcc.Name,
sdk.NewCoins(newFooCoin(100)),
),
)

suite.Require().Error(
suite.app.BankKeeper.MintCoins(
suite.ctx,
multiPermAcc.Name,
sdk.NewCoins(newBarCoin(100)),
),
)

}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}