Skip to content

Commit

Permalink
feat: strict inflation schedule (#1578)
Browse files Browse the repository at this point in the history
### Description

Part of celestiaorg/celestia-app#1570
Implement the strict inflation schedule specified in
celestiaorg/celestia-app#1584

### Remaining tasks
- [x] implement inflation rate change based on block timestamp rather
than block height
- [x] set genesis time in x/mint state
- [x] move `MintDenom` from param to `x/mint` state
- [x] remove `TestRandomizedGenState`
- [x] investigate why `TestQueryGRPC` / other integration tests are
never run
- [x] write a test that 1. starts a new chain, 2. verifies state of
x/mint module for first few blocks, 3. skips ahead to a block one year
later, 4. verifies inflation rate went down a bit
- [x] remove `Params` entirely
- [x] annual provisions for a year should be based on total supply at
the beginning of that year
- [x] for time-based inflation, calculate block provision as current
block timestamp - previous block timestamp = seconds elapsed / seconds
in a year * annual provisions for a year
- [x] remove `BlocksPerYear`
- [x] elaborate in README.md

### FLUPs

- celestiaorg/celestia-app#1755
- celestiaorg/celestia-app#1757
- celestiaorg/celestia-app#1758
- celestiaorg/celestia-app#1756

---------

Co-authored-by: Rootul P <rootulp@gmail.com>
Co-authored-by: Evan Forbes <42654277+evan-forbes@users.noreply.github.com>
Co-authored-by: Callum Waters <cmwaters19@gmail.com>
  • Loading branch information
4 people committed May 17, 2023
1 parent fbe7cf7 commit e870412
Show file tree
Hide file tree
Showing 36 changed files with 4,220 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ coverage.txt
tools-stamp
__debug_bin
profile.out
tmp/
run.sh
testing/e2e/networks/*/
square/testdata
16 changes: 10 additions & 6 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"os"
"path/filepath"

"github.com/celestiaorg/celestia-app/x/mint"
mintkeeper "github.com/celestiaorg/celestia-app/x/mint/keeper"
minttypes "github.com/celestiaorg/celestia-app/x/mint/types"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
Expand Down Expand Up @@ -53,9 +56,6 @@ import (
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govv1beta2 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
oldgovtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
"github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/params"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
Expand Down Expand Up @@ -308,8 +308,12 @@ func New(
appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName),
)
app.MintKeeper = mintkeeper.NewKeeper(
appCodec, keys[minttypes.StoreKey], app.GetSubspace(minttypes.ModuleName), &stakingKeeper,
app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName,
appCodec,
keys[minttypes.StoreKey],
&stakingKeeper,
app.AccountKeeper,
app.BankKeeper,
authtypes.FeeCollectorName,
)
app.DistrKeeper = distrkeeper.NewKeeper(
appCodec, keys[distrtypes.StoreKey], app.GetSubspace(distrtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
Expand Down Expand Up @@ -421,7 +425,7 @@ func New(
feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants),
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
Expand Down
6 changes: 3 additions & 3 deletions app/default_overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/json"

"github.com/celestiaorg/celestia-app/pkg/appconsts"
"github.com/celestiaorg/celestia-app/x/mint"
minttypes "github.com/celestiaorg/celestia-app/x/mint/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
Expand All @@ -14,8 +16,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/mint"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -95,7 +95,7 @@ type mintModule struct {
// DefaultGenesis returns custom x/mint module genesis state.
func (mintModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
genState := minttypes.DefaultGenesisState()
genState.Params.MintDenom = BondDenom
genState.Minter.BondDenom = BondDenom

return cdc.MustMarshalJSON(genState)
}
Expand Down
7 changes: 6 additions & 1 deletion app/test/std_sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,12 @@ func (s *StandardSDKIntegrationTestSuite) TestStandardSDK() {
name: "create legacy community spend governance proposal",
msgFunc: func() (msgs []sdk.Msg, signer string) {
account := s.unusedAccount()
coins := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1000000)))
// Note: this test depends on at least one coin being present
// in the community pool. Funds land in the community pool due
// to inflation so if 1 coin is not present in the community
// pool, consider expanding the block interval or waiting for
// more blocks to be produced prior to executing this test case.
coins := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1)))
content := disttypes.NewCommunityPoolSpendProposal(
"title",
"description",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ require (
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.102.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
13 changes: 13 additions & 0 deletions proto/celestia/mint/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";
package celestia.mint.v1;

import "gogoproto/gogo.proto";
import "celestia/mint/v1/mint.proto";

option go_package = "github.com/celestiaorg/celestia-app/x/mint/types";

// GenesisState defines the mint module's genesis state.
message GenesisState {
// minter is a space for holding current inflation information.
Minter minter = 1 [(gogoproto.nullable) = false];
}
36 changes: 36 additions & 0 deletions proto/celestia/mint/v1/mint.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
syntax = "proto3";
package celestia.mint.v1;

option go_package = "github.com/celestiaorg/celestia-app/x/mint/types";

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "google/protobuf/timestamp.proto";

// Minter represents the mint state.
message Minter {
// InflationRate is the rate at which new tokens should be minted for the
// current year. For example if InflationRate=0.1, then 10% of the total
// supply will be minted over the course of the year.
string inflation_rate = 1 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// AnnualProvisions is the total number of tokens to be minted in the current
// year due to inflation.
string annual_provisions = 2 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// GenesisTime is the timestamp of the genesis block.
google.protobuf.Timestamp genesis_time = 3 [(gogoproto.stdtime) = true];

// PreviousBlockTime is the timestamp of the previous block.
google.protobuf.Timestamp previous_block_time = 4 [(gogoproto.stdtime) = true];

// BondDenom is the denomination of the token that should be minted.
string bond_denom = 5;
}
58 changes: 58 additions & 0 deletions proto/celestia/mint/v1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
syntax = "proto3";
package celestia.mint.v1;

import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "celestia/mint/v1/mint.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/celestiaorg/celestia-app/x/mint/types";

// Query defines the gRPC querier service.
service Query {
// InflationRate returns the current inflation rate.
rpc InflationRate(QueryInflationRateRequest) returns (QueryInflationRateResponse) {
option (google.api.http).get = "/cosmos/mint/v1beta1/inflation_rate";
}

// AnnualProvisions returns the current annual provisions.
rpc AnnualProvisions(QueryAnnualProvisionsRequest) returns (QueryAnnualProvisionsResponse) {
option (google.api.http).get = "/cosmos/mint/v1beta1/annual_provisions";
}

// GenesisTime returns the genesis time.
rpc GenesisTime(QueryGenesisTimeRequest) returns (QueryGenesisTimeResponse) {
option (google.api.http).get = "/cosmos/mint/v1beta1/genesis_time";
}
}

// QueryInflationRateRequest is the request type for the Query/InflationRate RPC method.
message QueryInflationRateRequest {}

// QueryInflationRateResponse is the response type for the Query/InflationRate RPC
// method.
message QueryInflationRateResponse {
// InflationRate is the current inflation rate.
bytes inflation_rate = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

// QueryAnnualProvisionsRequest is the request type for the
// Query/AnnualProvisions RPC method.
message QueryAnnualProvisionsRequest {}

// QueryAnnualProvisionsResponse is the response type for the
// Query/AnnualProvisions RPC method.
message QueryAnnualProvisionsResponse {
// AnnualProvisions is the current annual provisions.
bytes annual_provisions = 1
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}

// QueryGenesisTimeRequest is the request type for the Query/GenesisTime RPC method.
message QueryGenesisTimeRequest {}

// QueryGenesisTimeResponse is the response type for the Query/GenesisTime RPC method.
message QueryGenesisTimeResponse {
// GenesisTime is the timestamp associated with the first block.
google.protobuf.Timestamp genesis_time = 1 [(gogoproto.stdtime) = true];
}
52 changes: 52 additions & 0 deletions x/mint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# `x/mint`

`x/mint` is a fork of the Cosmos SDK `x/mint` module that makes some changes to the inflation mechanism.

1. Remove all parameters from the module
1. Calculate inflation rate once per year based on the number of years since genesis
1. Calculate annual provisions once per year based on the total supply
1. Calculate block provision once per block based on the number of seconds elapsed between the current block and the previous block

## Constants

See [./types/constants.go](./types/constants.go) for the constants used in this module.

Note: this module assumes `DaysPerYear = 365.2425` so when modifying tests, developers must define durations based on this assumption because ordinary durations won't return the expected results. In other words:

```go
// oneYear is 31,556,952 seconds which will likely return expected results in tests
oneYear, err := time.ParseDuration(fmt.Sprintf("%vs", minttypes.SecondsPerYear))

// this oneYear is 31,536,000 seconds which will likely return unexpected results in tests
oneYear := time.Hour * 24 * 365
```

## Inflation Schedule

| Year | Inflation (%) |
|------|-------------------|
| 0 | 8.00 |
| 1 | 7.20 |
| 2 | 6.48 |
| 3 | 5.832 |
| 4 | 5.2488 |
| 5 | 4.72392 |
| 6 | 4.251528 |
| 7 | 3.8263752 |
| 8 | 3.44373768 |
| 9 | 3.099363912 |
| 10 | 2.7894275208 |
| 11 | 2.51048476872 |
| 12 | 2.259436291848 |
| 13 | 2.0334926626632 |
| 14 | 1.83014339639688 |
| 15 | 1.647129056757192 |
| 16 | 1.50 |
| 17 | 1.50 |
| 18 | 1.50 |
| 19 | 1.50 |
| 20 | 1.50 |

## References

1. ADR-019
93 changes: 93 additions & 0 deletions x/mint/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package mint

import (
"time"

"github.com/celestiaorg/celestia-app/x/mint/keeper"
"github.com/celestiaorg/celestia-app/x/mint/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// BeginBlocker updates the inflation rate, annual provisions, and then mints
// the block provision for the current block.
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

maybeSetGenesisTime(ctx, k)
maybeUpdateMinter(ctx, k)
mintBlockProvision(ctx, k)
setPreviousBlockTime(ctx, k)
}

// maybeSetGenesisTime sets the genesis time if the current block height is 1.
func maybeSetGenesisTime(ctx sdk.Context, k keeper.Keeper) {
if ctx.BlockHeight() == 1 {
genesisTime := ctx.BlockTime()
minter := k.GetMinter(ctx)
minter.GenesisTime = &genesisTime
k.SetMinter(ctx, minter)
}
}

// maybeUpdateMinter updates the inflation rate and annual provisions if the
// inflation rate has changed.
func maybeUpdateMinter(ctx sdk.Context, k keeper.Keeper) {
minter := k.GetMinter(ctx)
newInflationRate := minter.CalculateInflationRate(ctx)
if newInflationRate == minter.InflationRate {
// The minter's InflationRate AnnualProvisions already reflect the
// values for this year. Exit early because we don't need to update
// them.
return
}
minter.InflationRate = newInflationRate
k.SetMinter(ctx, minter)

totalSupply := k.StakingTokenSupply(ctx)
minter.AnnualProvisions = minter.CalculateAnnualProvisions(totalSupply)
k.SetMinter(ctx, minter)
}

// mintBlockProvision mints the block provision for the current block.
func mintBlockProvision(ctx sdk.Context, k keeper.Keeper) {
minter := k.GetMinter(ctx)
if minter.PreviousBlockTime == nil {
// exit early if previous block time is nil
// this is expected to happen for block height = 1
return
}

toMintCoin := minter.CalculateBlockProvision(ctx.BlockTime(), *minter.PreviousBlockTime)
toMintCoins := sdk.NewCoins(toMintCoin)

err := k.MintCoins(ctx, toMintCoins)
if err != nil {
panic(err)
}

err = k.SendCoinsToFeeCollector(ctx, toMintCoins)
if err != nil {
panic(err)
}

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

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeMint,
sdk.NewAttribute(types.AttributeKeyInflationRate, minter.InflationRate.String()),
sdk.NewAttribute(types.AttributeKeyAnnualProvisions, minter.AnnualProvisions.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, toMintCoin.Amount.String()),
),
)
}

func setPreviousBlockTime(ctx sdk.Context, k keeper.Keeper) {
minter := k.GetMinter(ctx)
blockTime := ctx.BlockTime()
minter.PreviousBlockTime = &blockTime
k.SetMinter(ctx, minter)
}
Loading

0 comments on commit e870412

Please sign in to comment.