-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat(x/mint)!: Replace InflationCalculationFn with MintFn + simple epoch minting #20363
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
Changes from 38 commits
603d9a7
ba0b474
cf5fd6b
63a3a89
4cd5f15
6734cab
0959509
dc3e06e
b063cbb
848cbf5
f2f8213
5ae7ce6
7f26121
5abb75f
593baab
61caa0b
969cb1d
faa40de
f89c958
183fb37
67aac7d
9696890
61dab4a
62c9b22
007d50e
aa7fccf
3d73011
18a741f
3b4264d
d80fe8d
ce185c2
ee8d6a6
a497b96
281557d
1c4e6c1
32fe4d9
dc14d2b
ace7bca
e5425a4
2c80fed
ec56d49
1c62f85
0931da6
c002f55
61250af
29970a6
043dc71
d9e30cb
712d5aa
a66e1e8
226091c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,7 +93,7 @@ type SimApp struct { | |
| ConsensusParamsKeeper consensuskeeper.Keeper | ||
| CircuitBreakerKeeper circuitkeeper.Keeper | ||
| PoolKeeper poolkeeper.Keeper | ||
| EpochsKeeper epochskeeper.Keeper | ||
| EpochsKeeper *epochskeeper.Keeper | ||
|
|
||
| // simulation manager | ||
| sm *module.SimulationManager | ||
|
|
@@ -111,7 +111,8 @@ func init() { | |
| // AppConfig returns the default app config. | ||
| func AppConfig() depinject.Config { | ||
| return depinject.Configs( | ||
| appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) | ||
| appConfig, // Alternatively use appconfig.LoadYAML(AppConfigYAML) | ||
| depinject.Provide(ProvideExampleMintFn), // optional: override the mint module's mint function with epoched minting | ||
|
Contributor
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. personal opinion: I have seen people just copying the app.go without thinking too much about the configuration. With this change, you make the new mint function default. This may cause some unexpected behaviour and risks.
Contributor
Author
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. I think it's fine if this becomes the new default for new apps, we'll get some good side effects from this. For example, we can stop producing empty blocks |
||
| ) | ||
| } | ||
|
|
||
|
|
@@ -174,8 +175,7 @@ func NewSimApp( | |
| // | ||
|
|
||
| // For providing a custom inflation function for x/mint add here your | ||
| // custom function that implements the minttypes.InflationCalculationFn | ||
| // interface. | ||
| // custom function that implements the minttypes.MintFn interface. | ||
| ), | ||
| ) | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| package simapp | ||
|
Contributor
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. Why is this in simapp? I would expect this code to be in the mint module.
Contributor
Author
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. 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.
Contributor
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. Thanks for the explanation! Personally, I would prefer something like |
||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/binary" | ||
|
|
||
| "cosmossdk.io/core/appmodule" | ||
| "cosmossdk.io/core/event" | ||
| "cosmossdk.io/math" | ||
| authtypes "cosmossdk.io/x/auth/types" | ||
| banktypes "cosmossdk.io/x/bank/types" | ||
| minttypes "cosmossdk.io/x/mint/types" | ||
| stakingtypes "cosmossdk.io/x/staking/types" | ||
|
|
||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| ) | ||
|
|
||
| type MintBankKeeper interface { | ||
| MintCoins(ctx context.Context, moduleName string, coins sdk.Coins) error | ||
| SendCoinsFromModuleToModule(ctx context.Context, senderModule string, recipientModule string, amt sdk.Coins) error | ||
| } | ||
|
|
||
| // ProvideExampleMintFn returns the function used in x/mint's endblocker to mint new tokens. | ||
|
alpe marked this conversation as resolved.
|
||
| // Note that this function can not have the mint keeper as a parameter because it would create a cyclic dependency. | ||
| func ProvideExampleMintFn(bankKeeper MintBankKeeper) minttypes.MintFn { | ||
| return func(ctx context.Context, env appmodule.Environment, minter *minttypes.Minter, epochID string, epochNumber int64) error { | ||
| // in this example we ignore epochNumber as we don't care what epoch we are in, we just assume we are being called every minute. | ||
| if epochID != "minute" { | ||
| return nil | ||
| } | ||
|
|
||
| var stakingParams stakingtypes.QueryParamsResponse | ||
| err := env.QueryRouterService.InvokeTyped(ctx, &stakingtypes.QueryParamsRequest{}, &stakingParams) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var bankSupply banktypes.QuerySupplyOfResponse | ||
| err = env.QueryRouterService.InvokeTyped(ctx, &banktypes.QuerySupplyOfRequest{Denom: stakingParams.Params.BondDenom}, &bankSupply) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| stakingTokenSupply := bankSupply.Amount | ||
|
|
||
| var mintParams minttypes.QueryParamsResponse | ||
| err = env.QueryRouterService.InvokeTyped(ctx, &minttypes.QueryParamsRequest{}, &mintParams) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var stakingPool stakingtypes.QueryPoolResponse | ||
| err = env.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) | ||
|
|
||
| // to get a more accurate amount of tokens minted, we get, and later store, last minting time. | ||
|
|
||
| // if this is the first time minting, we initialize the minter.Data with the current time - 60s | ||
| // to mint tokens at the beginning. Note: this is a custom behavior to avoid breaking tests. | ||
| if minter.Data == nil { | ||
| minter.Data = make([]byte, 8) | ||
| binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.Unix()-60)) | ||
| } | ||
|
|
||
| lastMint := binary.BigEndian.Uint64(minter.Data) | ||
| binary.BigEndian.PutUint64(minter.Data, (uint64)(env.HeaderService.HeaderInfo(ctx).Time.Unix())) | ||
|
|
||
| // calculate the amount of tokens to mint, based on the time since the last mint | ||
| secsSinceLastMint := env.HeaderService.HeaderInfo(ctx).Time.Unix() - (int64)(lastMint) | ||
| provisionAmt := minter.AnnualProvisions.QuoInt64(31536000).MulInt64(secsSinceLastMint) // 31536000 = seconds in a year | ||
| mintedCoin := sdk.NewCoin(mintParams.Params.MintDenom, provisionAmt.TruncateInt()) | ||
| maxSupply := mintParams.Params.MaxSupply | ||
| totalSupply := stakingTokenSupply.Amount | ||
|
|
||
| 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() { | ||
|
facundomedica marked this conversation as resolved.
|
||
| // 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 | ||
| } | ||
|
|
||
| // Example of custom send while minting | ||
| // Send some tokens to a "team account" | ||
| // if err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, ... ); err != nil { | ||
| // return err | ||
| // } | ||
|
|
||
| if err = bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, authtypes.FeeCollectorName, mintedCoins); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| 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()), | ||
| ) | ||
| } | ||
| } | ||
|
Contributor
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. Some unit tests on the edge cases would be great |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ func (s *E2ETestSuite) TestTotalSupplyGRPCHandler() { | |
| &types.QueryTotalSupplyResponse{ | ||
| Supply: sdk.NewCoins( | ||
| sdk.NewCoin(fmt.Sprintf("%stoken", val.GetMoniker()), s.cfg.AccountTokens), | ||
| sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(10))), | ||
| sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(123))), | ||
|
Contributor
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. why the increase ?
Contributor
Author
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. Because now it mints the equivalent of a minute in the first block (the beginning of the epoch) |
||
| ), | ||
| Pagination: &query.PageResponse{ | ||
| Total: 2, | ||
|
|
@@ -50,7 +50,7 @@ func (s *E2ETestSuite) TestTotalSupplyGRPCHandler() { | |
| }, | ||
| &types.QuerySupplyOfResponse{}, | ||
| &types.QuerySupplyOfResponse{ | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(10))), | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(123))), | ||
| }, | ||
| }, | ||
| { | ||
|
|
@@ -61,7 +61,7 @@ func (s *E2ETestSuite) TestTotalSupplyGRPCHandler() { | |
| }, | ||
| &types.QuerySupplyOfResponse{}, | ||
| &types.QuerySupplyOfResponse{ | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(20))), | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(123))), | ||
| }, | ||
| }, | ||
| { | ||
|
|
@@ -72,7 +72,7 @@ func (s *E2ETestSuite) TestTotalSupplyGRPCHandler() { | |
| }, | ||
| &types.QuerySupplyOfResponse{}, | ||
| &types.QuerySupplyOfResponse{ | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(10))), | ||
| Amount: sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(math.NewInt(123))), | ||
| }, | ||
| }, | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,7 @@ type ModuleInputs struct { | |
| type ModuleOutputs struct { | ||
| depinject.Out | ||
|
|
||
| EpochKeeper keeper.Keeper | ||
| EpochKeeper *keeper.Keeper | ||
|
Contributor
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. why does it need to be a pointer?
Contributor
Author
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. This was a bug with x/epochs, in which hooks weren't being set. The issue was that we were doing |
||
| Module appmodule.AppModule | ||
| } | ||
|
|
||
|
|
@@ -49,7 +49,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { | |
| return ModuleOutputs{EpochKeeper: k, Module: m} | ||
| } | ||
|
|
||
| func InvokeSetHooks(keeper keeper.Keeper, hooks map[string]types.EpochHooksWrapper) error { | ||
| func InvokeSetHooks(keeper *keeper.Keeper, hooks map[string]types.EpochHooksWrapper) error { | ||
|
facundomedica marked this conversation as resolved.
|
||
| if hooks == nil { | ||
| return nil | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.