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(x/mint)!: Replace InflationCalculationFn with MintFn + simple epoch minting #20363

Merged
merged 51 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
603d9a7
initial
facundomedica May 7, 2024
ba0b474
example in depinject
facundomedica May 7, 2024
cf5fd6b
fix
facundomedica May 7, 2024
63a3a89
progress
facundomedica May 13, 2024
4cd5f15
update
facundomedica May 13, 2024
6734cab
add arbritrary params, missing docs
facundomedica May 17, 2024
0959509
progress
facundomedica May 22, 2024
dc3e06e
progress
facundomedica May 22, 2024
b063cbb
finally it works
facundomedica May 23, 2024
848cbf5
lint fix and fix tests
facundomedica May 23, 2024
f2f8213
go mod tidy
facundomedica May 23, 2024
5ae7ce6
Merge branch 'main' of https://github.com/cosmos/cosmos-sdk into facu…
facundomedica May 23, 2024
7f26121
hopefully this is it
facundomedica May 23, 2024
5abb75f
hopefully this is it
facundomedica May 23, 2024
593baab
fix
facundomedica May 23, 2024
61caa0b
use before epoch start instead of end
facundomedica May 23, 2024
969cb1d
fix some e2e
facundomedica May 23, 2024
faa40de
seems to be working now
facundomedica May 23, 2024
f89c958
increase code coverage
facundomedica May 28, 2024
183fb37
lint fix
facundomedica May 28, 2024
67aac7d
increase test coverage and remove some unnecessary code
facundomedica May 30, 2024
9696890
forgot to add files hehe
facundomedica May 30, 2024
61dab4a
merge
facundomedica May 30, 2024
62c9b22
add minter data
facundomedica Jun 2, 2024
007d50e
fix
facundomedica Jun 2, 2024
aa7fccf
lint fix
facundomedica Jun 2, 2024
3d73011
fixes
facundomedica Jun 2, 2024
18a741f
fix comment
facundomedica Jun 2, 2024
3b4264d
suggestions
facundomedica Jun 3, 2024
d80fe8d
fix
facundomedica Jun 3, 2024
ce185c2
re-add inflation calculation function to reduce breakage
facundomedica Jun 3, 2024
ee8d6a6
fix tests
facundomedica Jun 3, 2024
a497b96
fix tests
facundomedica Jun 3, 2024
281557d
fixes
facundomedica Jun 3, 2024
1c4e6c1
another fix
facundomedica Jun 3, 2024
32fe4d9
comments
facundomedica Jun 3, 2024
dc14d2b
nit
facundomedica Jun 3, 2024
ace7bca
Update x/mint/CHANGELOG.md
facundomedica Jun 3, 2024
e5425a4
added readme
facundomedica Jun 3, 2024
2c80fed
Merge branch 'facu/exp-mintfn2' of https://github.com/cosmos/cosmos-s…
facundomedica Jun 3, 2024
ec56d49
fix test suite name
facundomedica Jun 3, 2024
1c62f85
Update x/mint/README.md
facundomedica Jun 3, 2024
0931da6
add diagram
facundomedica Jun 3, 2024
c002f55
Merge branch 'facu/exp-mintfn2' of https://github.com/cosmos/cosmos-s…
facundomedica Jun 3, 2024
61250af
conflicts
facundomedica Jun 7, 2024
29970a6
tidy
facundomedica Jun 7, 2024
043dc71
add doc
facundomedica Jun 11, 2024
d9e30cb
conflicts
facundomedica Jun 11, 2024
712d5aa
fix tests
facundomedica Jun 11, 2024
a66e1e8
a couple of tests
facundomedica Jun 11, 2024
226091c
fix inflation tests
facundomedica Jun 11, 2024
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
436 changes: 381 additions & 55 deletions api/cosmos/mint/v1beta1/mint.pulsar.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions simapp/app_di.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func init() {
func AppConfig() depinject.Config {
return depinject.Configs(
appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML)
depinject.Provide(ProvideMintFn),
)
}

Expand Down
116 changes: 116 additions & 0 deletions simapp/mint_fn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package simapp
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this in simapp? I would expect this code to be in the mint module.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because this is the example that developers can use to develop their own mint function. The idea here is that anyone creates their own minting function if they want to, and it works without requiring direct usage of keepers as it uses the query routers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation! Personally, I would prefer something like x/mint/example. I do understand your point of keeping it decoupled but it still relates more the mint package than to simapp for me. This is personal preference of course.


import (
"context"

"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/event"
"cosmossdk.io/math"
authtypes "cosmossdk.io/x/auth/types"
bankkeeper "cosmossdk.io/x/bank/keeper"
banktypes "cosmossdk.io/x/bank/types"
minttypes "cosmossdk.io/x/mint/types"
stakingtypes "cosmossdk.io/x/staking/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// ProvideMintFn returns the function used in x/mint's endblocker to mint new tokens.
// Note that this function can not have the mint keeper as a parameter because it would create a cyclic dependency.
func ProvideMintFn(bankKeeper bankkeeper.Keeper) minttypes.MintFn {
return func(ctx context.Context, env appmodule.Environment, minter *minttypes.Minter) error {
var stakingParams stakingtypes.QueryParamsResponse
err := env.RouterService.QueryRouterService().InvokeTyped(ctx, &stakingtypes.QueryParamsRequest{}, &stakingParams)
if err != nil {
return err
}

var bankSupply banktypes.QuerySupplyOfResponse
err = env.RouterService.QueryRouterService().InvokeTyped(ctx, &banktypes.QuerySupplyOfRequest{Denom: stakingParams.Params.BondDenom}, &bankSupply)
if err != nil {
return err
}
stakingTokenSupply := bankSupply.Amount

var mintParams minttypes.QueryParamsResponse
err = env.RouterService.QueryRouterService().InvokeTyped(ctx, &minttypes.QueryParamsRequest{}, &mintParams)
if err != nil {
return err
}

var stakingPool stakingtypes.QueryPoolResponse
err = env.RouterService.QueryRouterService().InvokeTyped(ctx, &stakingtypes.QueryPoolRequest{}, &stakingPool)
if err != nil {
return err
}

// bondedRatio
bondedRatio := math.LegacyNewDecFromInt(stakingPool.Pool.BondedTokens).QuoInt(stakingTokenSupply.Amount)
minter.Inflation = minter.NextInflationRate(mintParams.Params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(mintParams.Params, stakingTokenSupply.Amount)
// TODO: store minter afterwards

mintedCoin := minter.BlockProvision(mintParams.Params)
mintedCoins := sdk.NewCoins(mintedCoin)
maxSupply := mintParams.Params.MaxSupply
totalSupply := stakingTokenSupply.Amount

// if maxSupply is not infinite, check against max_supply parameter
if !maxSupply.IsZero() {
if totalSupply.Add(mintedCoins.AmountOf(mintParams.Params.MintDenom)).GT(maxSupply) {
// calculate the difference between maxSupply and totalSupply
diff := maxSupply.Sub(totalSupply)
Copy link
Contributor

Choose a reason for hiding this comment

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

I have not seen a check in the module that ensures maxSupply > totalSupply. Therefore this can be negative 🙈

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought that I had pushed that fix, found it yesterday while adding more tests 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking at it again, no, I didn't add a fix. Should we return an error if totalSupply > maxSupply here? It should never be the case tho, as the minting only happens here and we don't mint over maxSupply

// mint the difference
diffCoin := sdk.NewCoin(mintParams.Params.MintDenom, diff)
diffCoins := sdk.NewCoins(diffCoin)

// mint coins
if diffCoins.Empty() {
// skip as no coins need to be minted
return nil
}

if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, diffCoins); err != nil {
return err
}
mintedCoins = diffCoins
}
}

// mint coins if maxSupply is infinite or total staking supply is less than maxSupply
if maxSupply.IsZero() || totalSupply.Add(mintedCoins.AmountOf(mintParams.Params.MintDenom)).LT(maxSupply) {
Copy link
Contributor

Choose a reason for hiding this comment

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

when total + minted == max supply, then neither L67 or this condition fits and no coins would be minted although a sub amount would be possible. I think L67 should handle this

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm simplifying it like this:

		if !maxSupply.IsZero() {
			// supply is not infinite, check the amount to mint
			remainingSupply := maxSupply.Sub(totalSupply)

			if remainingSupply.LTE(math.ZeroInt()) {
				// max supply reached, no new tokens will be minted
				// also handles the case where totalSupply > maxSupply
				return nil
			}

			// if the amount to mint is greater than the remaining supply, mint the remaining supply
			if mintedCoin.Amount.GT(remainingSupply) {
				mintedCoin.Amount = remainingSupply
			}
		}

		if mintedCoin.Amount.IsZero() {
			// skip as no coins need to be minted
			return nil
		}

		mintedCoins := sdk.NewCoins(mintedCoin)
		if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, mintedCoins); err != nil {
			return err
		}

// mint coins
if mintedCoins.Empty() {
// skip as no coins need to be minted
return nil
}

if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, mintedCoins); err != nil {
return err
}
}

// Example of custom send while minting
// Send some tokens to a "team account"
// if err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, ... ); err != nil {
// return err
// }

// TODO: figure how to get FeeCollectorName from mint module without generating a cyclic dependency
facundomedica marked this conversation as resolved.
Show resolved Hide resolved
if err = bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, authtypes.FeeCollectorName, mintedCoins); err != nil {
return err
}

if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(minttypes.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
Copy link
Contributor

Choose a reason for hiding this comment

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

should be be called in bank.MintCoins instead?

}
facundomedica marked this conversation as resolved.
Show resolved Hide resolved

return env.EventService.EventManager(ctx).EmitKV(
minttypes.EventTypeMint,
event.NewAttribute(minttypes.AttributeKeyBondedRatio, bondedRatio.String()),
event.NewAttribute(minttypes.AttributeKeyInflation, minter.Inflation.String()),
event.NewAttribute(minttypes.AttributeKeyAnnualProvisions, minter.AnnualProvisions.String()),
event.NewAttribute(sdk.AttributeKeyAmount, mintedCoin.Amount.String()),
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Some unit tests on the edge cases would be great

14 changes: 7 additions & 7 deletions x/mint/depinject.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ func init() {
type ModuleInputs struct {
depinject.In

ModuleKey depinject.OwnModuleKey
Config *modulev1.Module
Environment appmodule.Environment
Cdc codec.Codec
InflationCalculationFn types.InflationCalculationFn `optional:"true"`
ModuleKey depinject.OwnModuleKey
Config *modulev1.Module
Environment appmodule.Environment
Cdc codec.Codec
MintFn types.MintFn `optional:"true"`
facundomedica marked this conversation as resolved.
Show resolved Hide resolved

AccountKeeper types.AccountKeeper
BankKeeper types.BankKeeper
Expand Down Expand Up @@ -71,8 +71,8 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
as,
)

// when no inflation calculation function is provided it will use the default types.DefaultInflationCalculationFn
m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.InflationCalculationFn)
// when no mint function is provided it will use the default keeper.DefaultMintFn
m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.MintFn)

return ModuleOutputs{MintKeeper: k, Module: m}
}
74 changes: 3 additions & 71 deletions x/mint/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import (
"context"

"cosmossdk.io/core/event"
"cosmossdk.io/x/mint/types"

"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// BeginBlocker mints new tokens for the previous block.
func (k Keeper) BeginBlocker(ctx context.Context, ic types.InflationCalculationFn) error {
func (k Keeper) BeginBlocker(ctx context.Context, ic types.MintFn) error {
defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyBeginBlocker)

// fetch stored minter & params
Expand All @@ -20,76 +18,10 @@
return err
}

params, err := k.Params.Get(ctx)
err = ic(ctx, k.Environment, &minter)
if err != nil {
return err
}

// recalculate inflation rate
totalStakingSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}

bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}

// update minter's inflation and annual provisions
minter.Inflation = ic(ctx, minter, params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalStakingSupply)
if err = k.Minter.Set(ctx, minter); err != nil {
return err
}

// calculate minted coins
mintedCoin := minter.BlockProvision(params)
mintedCoins := sdk.NewCoins(mintedCoin)

maxSupply := params.MaxSupply
totalSupply := k.bankKeeper.GetSupply(ctx, params.MintDenom).Amount // fetch total supply from the bank module

// if maxSupply is not infinite, check against max_supply parameter
if !maxSupply.IsZero() {
if totalSupply.Add(mintedCoins.AmountOf(params.MintDenom)).GT(maxSupply) {
// calculate the difference between maxSupply and totalSupply
diff := maxSupply.Sub(totalSupply)
// mint the difference
diffCoin := sdk.NewCoin(params.MintDenom, diff)
diffCoins := sdk.NewCoins(diffCoin)

// mint coins
if err := k.MintCoins(ctx, diffCoins); err != nil {
return err
}
mintedCoins = diffCoins
}
}

// mint coins if maxSupply is infinite or total staking supply is less than maxSupply
if maxSupply.IsZero() || totalSupply.Add(mintedCoins.AmountOf(params.MintDenom)).LT(maxSupply) {
// mint coins
if err := k.MintCoins(ctx, mintedCoins); err != nil {
return err
}
}

// send the minted coins to the fee collector account
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}

if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}

return k.EventService.EventManager(ctx).EmitKV(
types.EventTypeMint,
event.NewAttribute(types.AttributeKeyBondedRatio, bondedRatio.String()),
event.NewAttribute(types.AttributeKeyInflation, minter.Inflation.String()),
event.NewAttribute(types.AttributeKeyAnnualProvisions, minter.AnnualProvisions.String()),
event.NewAttribute(sdk.AttributeKeyAmount, mintedCoin.Amount.String()),
)
return k.Minter.Set(ctx, minter)
Dismissed Show dismissed Hide dismissed
}
71 changes: 71 additions & 0 deletions x/mint/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (

"cosmossdk.io/collections"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/event"
"cosmossdk.io/log"
"cosmossdk.io/math"
"cosmossdk.io/x/mint/types"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -101,3 +103,72 @@ func (k Keeper) MintCoins(ctx context.Context, newCoins sdk.Coins) error {
func (k Keeper) AddCollectedFees(ctx context.Context, fees sdk.Coins) error {
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.feeCollectorName, fees)
}

func (k Keeper) DefaultMintFn(ctx context.Context, env appmodule.Environment, minter *types.Minter) error {
stakingTokenSupply, err := k.StakingTokenSupply(ctx)
if err != nil {
return err
}

bondedRatio, err := k.BondedRatio(ctx)
if err != nil {
return err
}

params, err := k.Params.Get(ctx)
if err != nil {
return err
}

minter.Inflation = minter.NextInflationRate(params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, stakingTokenSupply)

mintedCoin := minter.BlockProvision(params)
mintedCoins := sdk.NewCoins(mintedCoin)
maxSupply := params.MaxSupply
totalSupply := stakingTokenSupply

// if maxSupply is not infinite, check against max_supply parameter
if !maxSupply.IsZero() {
if totalSupply.Add(mintedCoins.AmountOf(params.MintDenom)).GT(maxSupply) {
// calculate the difference between maxSupply and totalSupply
diff := maxSupply.Sub(totalSupply)
// mint the difference
diffCoin := sdk.NewCoin(params.MintDenom, diff)
diffCoins := sdk.NewCoins(diffCoin)

// mint coins
if err := k.MintCoins(ctx, diffCoins); err != nil {
return err
}
mintedCoins = diffCoins
}
}

// mint coins if maxSupply is infinite or total staking supply is less than maxSupply
if maxSupply.IsZero() || totalSupply.Add(mintedCoins.AmountOf(params.MintDenom)).LT(maxSupply) {
Copy link
Contributor

Choose a reason for hiding this comment

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

same issue when total+minted == max that is ignored

Copy link
Member Author

Choose a reason for hiding this comment

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

Left this one as is to avoid changing the current behavior

// mint coins
if err := k.MintCoins(ctx, mintedCoins); err != nil {
return err
}
}

// send the minted coins to the fee collector account
// TODO: figure out a better way to do this
err = k.AddCollectedFees(ctx, mintedCoins)
if err != nil {
return err
}

if mintedCoin.Amount.IsInt64() {
defer telemetry.ModuleSetGauge(types.ModuleName, float32(mintedCoin.Amount.Int64()), "minted_tokens")
}

return env.EventService.EventManager(ctx).EmitKV(
types.EventTypeMint,
event.NewAttribute(types.AttributeKeyBondedRatio, bondedRatio.String()),
event.NewAttribute(types.AttributeKeyInflation, minter.Inflation.String()),
event.NewAttribute(types.AttributeKeyAnnualProvisions, minter.AnnualProvisions.String()),
event.NewAttribute(sdk.AttributeKeyAmount, mintedCoin.Amount.String()),
)
}
25 changes: 13 additions & 12 deletions x/mint/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,29 @@ type AppModule struct {
keeper keeper.Keeper
authKeeper types.AccountKeeper

// inflationCalculator is used to calculate the inflation rate during BeginBlock.
// If inflationCalculator is nil, the default inflation calculation logic is used.
inflationCalculator types.InflationCalculationFn
// mintFn is used to mint new coins during BeginBlock. This function is in charge of
// minting new coins based on arbitrary logic, previously done through InflationCalculationFn.
// If mintFn is nil, the default minting logic is used.
mintFn types.MintFn
}

// NewAppModule creates a new AppModule object.
// If the InflationCalculationFn argument is nil, then the SDK's default inflation function will be used.
// If the mintFn argument is nil, then the SDK's default minting function will be used.
func NewAppModule(
cdc codec.Codec,
keeper keeper.Keeper,
ak types.AccountKeeper,
ic types.InflationCalculationFn,
mintFn types.MintFn,
) AppModule {
if ic == nil {
ic = types.DefaultInflationCalculationFn
if mintFn == nil {
mintFn = keeper.DefaultMintFn
}

return AppModule{
cdc: cdc,
keeper: keeper,
authKeeper: ak,
inflationCalculator: ic,
cdc: cdc,
keeper: keeper,
authKeeper: ak,
mintFn: mintFn,
}
}

Expand Down Expand Up @@ -156,7 +157,7 @@ func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion }

// BeginBlock returns the begin blocker for the mint module.
func (am AppModule) BeginBlock(ctx context.Context) error {
return am.keeper.BeginBlocker(ctx, am.inflationCalculator)
return am.keeper.BeginBlocker(ctx, am.mintFn)
Dismissed Show dismissed Hide dismissed
}

// AppModuleSimulation functions
Expand Down
2 changes: 2 additions & 0 deletions x/mint/proto/cosmos/mint/v1beta1/mint.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ message Minter {
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];

map<string, string> arbitrary_params = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Addition of arbitrary_params to the Minter message enhances customization. Consider documenting its intended use cases.

+ // arbitrary_params can be used to pass custom parameters for minting operations.

Committable suggestion was skipped due low confidence.

}

// Params defines the parameters for the x/mint module.
Expand Down
Loading
Loading