Skip to content

Commit

Permalink
fix(inflation): enable inflationary NIBI (#1655)
Browse files Browse the repository at this point in the history
* chore(epochs): add day epoch and remove 15min epoch from default genesis

* chore(epochs): remove 15min epoch from default genesis

* fix(inflation): inflate strategic reserves to sudo root account

* fix(inflation): test inflation amounts

* chore: update changelog
  • Loading branch information
k-yang authored Oct 31, 2023
1 parent 5ea9009 commit 2b99ca7
Show file tree
Hide file tree
Showing 16 changed files with 157 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [#1606](https://github.com/NibiruChain/nibiru/pull/1606) - fix(perp): emit `MarketUpdatedEvent` in the absence of index price
* [#1649](https://github.com/NibiruChain/nibiru/pull/1649) - fix(ledger): fix ledger for newer macos versions
* [#1655](https://github.com/NibiruChain/nibiru/pull/1655) - fix(inflation): inflate NIBI correctly to strategic treasury account

## [v0.21.10]

Expand Down
10 changes: 5 additions & 5 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,15 +368,15 @@ func (app *NibiruApp) InitKeepers(
app.AccountKeeper, app.BankKeeper, app.OracleKeeper, app.EpochsKeeper,
)

app.InflationKeeper = inflationkeeper.NewKeeper(
appCodec, keys[inflationtypes.StoreKey], app.GetSubspace(inflationtypes.ModuleName),
app.AccountKeeper, app.BankKeeper, app.DistrKeeper, app.stakingKeeper, authtypes.FeeCollectorName,
)

app.SudoKeeper = keeper.NewKeeper(
appCodec, keys[sudotypes.StoreKey],
)

app.InflationKeeper = inflationkeeper.NewKeeper(
appCodec, keys[inflationtypes.StoreKey], app.GetSubspace(inflationtypes.ModuleName),
app.AccountKeeper, app.BankKeeper, app.DistrKeeper, app.stakingKeeper, app.SudoKeeper, authtypes.FeeCollectorName,
)

app.EpochsKeeper.SetHooks(
epochstypes.NewMultiEpochHooks(
app.PerpKeeperV2.Hooks(),
Expand Down
8 changes: 4 additions & 4 deletions x/epochs/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ func TestEpochsExportGenesis(t *testing.T) {
require.Len(t, genesis.Epochs, 3)

errMsg := fmt.Sprintf("app.EpochsKeeper.AllEpochInfos(ctx): %v\n", app.EpochsKeeper.AllEpochInfos(ctx))
require.Equal(t, genesis.Epochs[0].Identifier, "15 min")
require.Equal(t, genesis.Epochs[0].Identifier, "30 min")
require.Equal(t, genesis.Epochs[0].StartTime, chainStartTime, errMsg)
require.Equal(t, genesis.Epochs[0].Duration, time.Minute*15, errMsg)
require.Equal(t, genesis.Epochs[0].Duration, time.Minute*30, errMsg)
require.Equal(t, genesis.Epochs[0].CurrentEpoch, uint64(0))
require.Equal(t, genesis.Epochs[0].CurrentEpochStartHeight, int64(0))
require.Equal(t, genesis.Epochs[0].CurrentEpochStartTime, chainStartTime)
require.Equal(t, genesis.Epochs[0].EpochCountingStarted, false)

require.Equal(t, genesis.Epochs[1].Identifier, "30 min")
require.Equal(t, genesis.Epochs[1].Identifier, "day")
require.Equal(t, genesis.Epochs[1].StartTime, chainStartTime, errMsg)
require.Equal(t, genesis.Epochs[1].Duration, time.Minute*30)
require.Equal(t, genesis.Epochs[1].Duration, time.Hour*24)
require.Equal(t, genesis.Epochs[1].CurrentEpoch, uint64(0))
require.Equal(t, genesis.Epochs[1].CurrentEpochStartHeight, int64(0))
require.Equal(t, genesis.Epochs[1].CurrentEpochStartTime, chainStartTime, errMsg)
Expand Down
10 changes: 5 additions & 5 deletions x/epochs/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ func TestQueryEpochInfos(t *testing.T) {
// check if EpochInfos are correct
require.Equal(t, epochInfosResponse.Epochs[0].StartTime, chainStartTime, errMsg)
require.Equal(t, epochInfosResponse.Epochs[0].CurrentEpochStartTime, chainStartTime)
require.Equal(t, epochInfosResponse.Epochs[0].Identifier, "15 min")
require.Equal(t, epochInfosResponse.Epochs[0].Duration, time.Minute*15)
require.Equal(t, epochInfosResponse.Epochs[0].Identifier, "30 min")
require.Equal(t, epochInfosResponse.Epochs[0].Duration, time.Minute*30)
require.Equal(t, epochInfosResponse.Epochs[0].CurrentEpoch, uint64(0))
require.Equal(t, epochInfosResponse.Epochs[0].EpochCountingStarted, false)

require.Equal(t, epochInfosResponse.Epochs[1].StartTime, chainStartTime)
require.Equal(t, epochInfosResponse.Epochs[1].CurrentEpochStartTime, chainStartTime)
require.Equal(t, epochInfosResponse.Epochs[1].Identifier, "30 min")
require.Equal(t, epochInfosResponse.Epochs[1].Duration, time.Minute*30)
require.Equal(t, epochInfosResponse.Epochs[1].Identifier, "day")
require.Equal(t, epochInfosResponse.Epochs[1].Duration, time.Hour*24)
require.Equal(t, epochInfosResponse.Epochs[1].CurrentEpoch, uint64(0))
require.Equal(t, epochInfosResponse.Epochs[1].EpochCountingStarted, false)
}
Expand All @@ -57,7 +57,7 @@ func TestCurrentEpochQuery(t *testing.T) {
queryClient := epochstypes.NewQueryClient(queryHelper)

// Valid epoch
epochInfosResponse, err := queryClient.CurrentEpoch(gocontext.Background(), &epochstypes.QueryCurrentEpochRequest{Identifier: "15 min"})
epochInfosResponse, err := queryClient.CurrentEpoch(gocontext.Background(), &epochstypes.QueryCurrentEpochRequest{Identifier: "30 min"})
require.NoError(t, err)
require.Equal(t, epochInfosResponse.CurrentEpoch, uint64(0))

Expand Down
4 changes: 2 additions & 2 deletions x/epochs/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func TestUpsertEpochInfo_HappyPath(t *testing.T) {

require.Len(t, allEpochs, 4)
// Epochs are ordered in alphabetical order
require.Equal(t, "15 min", allEpochs[0].Identifier)
require.Equal(t, "30 min", allEpochs[1].Identifier)
require.Equal(t, "30 min", allEpochs[0].Identifier)
require.Equal(t, "day", allEpochs[1].Identifier)
require.Equal(t, "monthly", allEpochs[2].Identifier)
require.Equal(t, "week", allEpochs[3].Identifier)
}
Expand Down
2 changes: 1 addition & 1 deletion x/epochs/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val

// AppModuleSimulation functions

// GenerateGenesisState creates a randomized GenState of the pool-incentives module.
// GenerateGenesisState creates a randomized GenState of the epochs module.
func (AppModule) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
Expand Down
8 changes: 4 additions & 4 deletions x/epochs/types/epochinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ func TestDefaultGenesis(t *testing.T) {

expectedEpochs := []EpochInfo{
{
Identifier: FifteenMinuteEpochID,
Identifier: ThirtyMinuteEpochID,
StartTime: time.Time{},
Duration: 15 * time.Minute,
Duration: 30 * time.Minute,
CurrentEpoch: 0,
CurrentEpochStartHeight: 0,
CurrentEpochStartTime: time.Time{},
EpochCountingStarted: false,
},
{
Identifier: ThirtyMinuteEpochID,
Identifier: DayEpochID,
StartTime: time.Time{},
Duration: 30 * time.Minute,
Duration: 24 * time.Hour,
CurrentEpoch: 0,
CurrentEpochStartHeight: 0,
CurrentEpochStartTime: time.Time{},
Expand Down
8 changes: 4 additions & 4 deletions x/epochs/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ func DefaultGenesis() *GenesisState {
func DefaultGenesisFromTime(startTime time.Time) *GenesisState {
epochs := []EpochInfo{
{
Identifier: FifteenMinuteEpochID,
Identifier: ThirtyMinuteEpochID,
StartTime: startTime,
Duration: 15 * time.Minute,
Duration: 30 * time.Minute,
CurrentEpoch: 0,
CurrentEpochStartHeight: 0,
CurrentEpochStartTime: startTime,
EpochCountingStarted: false,
},
{
Identifier: ThirtyMinuteEpochID,
Identifier: DayEpochID,
StartTime: startTime,
Duration: 30 * time.Minute,
Duration: 24 * time.Hour,
CurrentEpoch: 0,
CurrentEpochStartHeight: 0,
CurrentEpochStartTime: startTime,
Expand Down
12 changes: 6 additions & 6 deletions x/inflation/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb
Amount: epochMintProvision.TruncateInt(),
}

staking, incentives, communityPool, err := k.MintAndAllocateInflation(ctx, mintedCoin, params)
staking, strategic, communityPool, err := k.MintAndAllocateInflation(ctx, mintedCoin, params)
if err != nil {
k.Logger(ctx).Error(
"SKIPPING INFLATION: failed to mint and allocate inflation",
Expand Down Expand Up @@ -87,7 +87,7 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb

defer func() {
stakingAmt := staking.Amount
incentivesAmt := incentives.Amount
strategicAmt := strategic.Amount
cpAmt := communityPool.Amount

if mintedCoin.Amount.IsInt64() {
Expand All @@ -104,10 +104,10 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb
[]metrics.Label{telemetry.NewLabel("denom", mintedCoin.Denom)},
)
}
if incentivesAmt.IsInt64() {
if strategicAmt.IsInt64() {
telemetry.IncrCounterWithLabels(
[]string{types.ModuleName, "allocate", "incentives", "total"},
float32(incentivesAmt.Int64()),
[]string{types.ModuleName, "allocate", "strategic", "total"},
float32(strategicAmt.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", mintedCoin.Denom)},
)
}
Expand All @@ -132,7 +132,7 @@ func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumb

// ___________________________________________________________________________________________________

// Hooks wrapper struct for incentives keeper
// Hooks wrapper struct for inflation keeper
type Hooks struct {
k Keeper
}
Expand Down
24 changes: 17 additions & 7 deletions x/inflation/keeper/inflation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ func (k Keeper) MintAndAllocateInflation(
return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err
}

// Allocate minted coins according to allocation proportions (staking, usage
// incentives, community pool)
// Allocate minted coins according to allocation proportions (staking, strategic, community pool)
return k.AllocateExponentialInflation(ctx, coins, params)
}

Expand All @@ -42,7 +41,7 @@ func (k Keeper) MintCoins(ctx sdk.Context, coin sdk.Coin) error {
// AllocateExponentialInflation allocates coins from the inflation to external
// modules according to allocation proportions:
// - staking rewards -> sdk `auth` module fee collector
// - usage incentives -> `x/incentives` module
// - strategic reserves -> root account of x/sudo module
// - community pool -> `sdk `distr` module community pool
func (k Keeper) AllocateExponentialInflation(
ctx sdk.Context,
Expand All @@ -53,7 +52,7 @@ func (k Keeper) AllocateExponentialInflation(
err error,
) {
inflationDistribution := params.InflationDistribution
moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName)
inflationModuleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName)
// Allocate staking rewards into fee collector account
staking = k.GetProportions(ctx, mintedCoin, inflationDistribution.StakingRewards)

Expand All @@ -72,13 +71,24 @@ func (k Keeper) AllocateExponentialInflation(
if err = k.distrKeeper.FundCommunityPool(
ctx,
sdk.NewCoins(community),
moduleAddr,
inflationModuleAddr,
); err != nil {
return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err
}

// Remaining balance is strategic reserve allocation
strategic = k.bankKeeper.GetBalance(ctx, moduleAddr, denoms.NIBI)
// Remaining balance is strategic reserve allocation to the root account of the x/sudo module
strategic = k.bankKeeper.GetBalance(ctx, inflationModuleAddr, denoms.NIBI)
strategicAccountAddr, err := k.sudoKeeper.GetRoot(ctx)
if err != nil {
k.Logger(ctx).Error("get root account error", "error", err)
return staking, strategic, community, nil
}

if err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, strategicAccountAddr, sdk.NewCoins(strategic)); err != nil {
k.Logger(ctx).Error("send coins to root account error", "error", err)
return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, nil
}

return staking, strategic, community, nil
}

Expand Down
101 changes: 74 additions & 27 deletions x/inflation/keeper/inflation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,58 +14,105 @@ import (
"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/x/common/denoms"
"github.com/NibiruChain/nibiru/x/common/testutil/testapp"
types "github.com/NibiruChain/nibiru/x/inflation/types"
"github.com/NibiruChain/nibiru/x/inflation/types"
sudotypes "github.com/NibiruChain/nibiru/x/sudo/types"
)

func init() {
testapp.EnsureNibiruPrefix()
}

func TestMintAndAllocateInflation(t *testing.T) {
testCases := []struct {
name string
mintCoin sdk.Coin
expStakingRewardAmt sdk.Coin
expStrategicReservesAmt sdk.Coin
expCommunityPoolAmt sdk.DecCoins
name string
coinsToMint sdk.Coin
expectedStakingAmt sdk.Coin
expectedStrategicAmt sdk.Coin
expectedCommunityAmt sdk.Coin
expectedStakingRewardsBalance sdk.Coin
expectedStrategicReservesBalance sdk.Coin
expectedCommunityPoolBalance sdk.DecCoins
rootAccount string
}{
{
"pass",
sdk.NewCoin(denoms.NIBI, sdk.NewInt(1_000_000)),
sdk.NewCoin(denoms.NIBI, sdk.NewInt(278_000)),
sdk.NewCoin(denoms.NIBI, sdk.NewInt(100_000)),
sdk.NewDecCoins(sdk.NewDecCoin(denoms.NIBI, sdk.NewInt(622_000))),
name: "pass",
coinsToMint: sdk.NewCoin(denoms.NIBI, sdk.NewInt(1_000_000)),
expectedStakingAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(278_000)),
expectedStrategicAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(100_000)),
expectedCommunityAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(622_000)),
expectedStakingRewardsBalance: sdk.NewCoin(denoms.NIBI, sdk.NewInt(278_000)),
expectedStrategicReservesBalance: sdk.NewCoin(denoms.NIBI, sdk.NewInt(100_000)),
expectedCommunityPoolBalance: sdk.NewDecCoins(sdk.NewDecCoin(denoms.NIBI, sdk.NewInt(622_000))),
rootAccount: "nibi1qyqf35fkhn73hjr70442fctpq8prpqr9ysj9sn",
},
{
"pass - no coins minted ",
sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
sdk.DecCoins(nil),
name: "pass - no coins minted ",
coinsToMint: sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
expectedStakingAmt: sdk.Coin{},
expectedStrategicAmt: sdk.Coin{},
expectedCommunityAmt: sdk.Coin{},
expectedStakingRewardsBalance: sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
expectedStrategicReservesBalance: sdk.NewCoin(denoms.NIBI, sdk.ZeroInt()),
expectedCommunityPoolBalance: nil,
rootAccount: "nibi1qyqf35fkhn73hjr70442fctpq8prpqr9ysj9sn",
},
{
name: "pass - no root account",
coinsToMint: sdk.NewCoin(denoms.NIBI, sdk.NewInt(1_000_000)),
expectedStakingAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(278_000)),
expectedStrategicAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(100_000)),
expectedCommunityAmt: sdk.NewCoin(denoms.NIBI, sdk.NewInt(622_000)),
expectedStakingRewardsBalance: sdk.NewCoin(denoms.NIBI, sdk.NewInt(278_000)),
expectedStrategicReservesBalance: sdk.NewCoin(denoms.NIBI, sdk.NewInt(100_000)),
expectedCommunityPoolBalance: sdk.NewDecCoins(sdk.NewDecCoin(denoms.NIBI, sdk.NewInt(622_000))),
rootAccount: "",
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) {
nibiruApp, ctx := testapp.NewNibiruTestAppAndContext()

_, _, _, err := nibiruApp.InflationKeeper.MintAndAllocateInflation(ctx, tc.mintCoin, types.DefaultParams())
if tc.rootAccount != "" {
t.Logf("setting root account to %s", tc.rootAccount)
nibiruApp.SudoKeeper.Sudoers.Set(ctx, sudotypes.Sudoers{
Root: sdk.MustAccAddressFromBech32(tc.rootAccount).String(),
Contracts: []string{},
})
}

staking, strategic, community, err := nibiruApp.InflationKeeper.MintAndAllocateInflation(ctx, tc.coinsToMint, types.DefaultParams())
require.NoError(t, err)
assert.Equal(t, tc.expectedStakingAmt, staking)
assert.Equal(t, tc.expectedStrategicAmt, strategic)
assert.Equal(t, tc.expectedCommunityAmt, community)

// Get balances
balanceInflationModule := nibiruApp.BankKeeper.GetBalance(
ctx,
nibiruApp.AccountKeeper.GetModuleAddress(types.ModuleName),
denoms.NIBI,
)
var balanceStrategicReserve sdk.Coin
if tc.rootAccount != "" {
strategicAccount, err := nibiruApp.SudoKeeper.GetRoot(ctx)
require.NoError(t, err)
balanceStrategicReserve = nibiruApp.BankKeeper.GetBalance(
ctx,
strategicAccount,
denoms.NIBI,
)
} else {
// if no root account is specified, then the strategic reserve remains in the x/inflation module account
balanceStrategicReserve = nibiruApp.BankKeeper.GetBalance(ctx, nibiruApp.AccountKeeper.GetModuleAddress(types.ModuleName), denoms.NIBI)
}

feeCollector := nibiruApp.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName)
balanceStakingRewards := nibiruApp.BankKeeper.GetBalance(
ctx,
feeCollector,
nibiruApp.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName),
denoms.NIBI,
)

balanceCommunityPool := nibiruApp.DistrKeeper.GetFeePoolCommunityCoins(ctx)

require.NoError(t, err, tc.name)
assert.Equal(t, tc.expStakingRewardAmt, balanceStakingRewards)
assert.Equal(t, tc.expStrategicReservesAmt, balanceInflationModule)
assert.Equal(t, tc.expCommunityPoolAmt, balanceCommunityPool)
assert.Equal(t, tc.expectedStakingRewardsBalance, balanceStakingRewards)
assert.Equal(t, tc.expectedStrategicReservesBalance, balanceStrategicReserve)
assert.Equal(t, tc.expectedCommunityPoolBalance, balanceCommunityPool)
})
}
}
Expand Down
Loading

0 comments on commit 2b99ca7

Please sign in to comment.