Skip to content

Commit

Permalink
Merge branch 'master' into fix/fix-version-at-genesis
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasmatt authored Nov 22, 2023
2 parents 800f9fc + f1f17bd commit c8c8c2e
Show file tree
Hide file tree
Showing 18 changed files with 2,210 additions and 310 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#1573](https://github.com/NibiruChain/nibiru/pull/1573) - feat(perp): Close markets and compute settlement price
* [#1632](https://github.com/NibiruChain/nibiru/pull/1632) - feat(perp): Add settle position transaction
* [#1656](https://github.com/NibiruChain/nibiru/pull/1656) - feat(perp): Make the collateral denom a stateful collections.Item
* [#1663](https://github.com/NibiruChain/nibiru/pull/1663) - feat(perp): Add volume based rebates
* [#1670](https://github.com/NibiruChain/nibiru/pull/1670) - feat(inflation): Make inflation polynomial

### State Machine Breaking
Expand Down
26 changes: 14 additions & 12 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,10 +802,12 @@ func ModuleAccPerms() map[string][]string {
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
ibcfeetypes.ModuleName: {},

perptypes.ModuleName: {},
perptypes.VaultModuleAccount: {},
perptypes.PerpEFModuleAccount: {},
perptypes.FeePoolModuleAccount: {},
perptypes.ModuleName: {},
perptypes.VaultModuleAccount: {},
perptypes.PerpEFModuleAccount: {},
perptypes.FeePoolModuleAccount: {},
perptypes.DNRAllocationModuleAccount: {},
perptypes.DNREscrowModuleAccount: {},

epochstypes.ModuleName: {},
sudotypes.ModuleName: {},
Expand Down Expand Up @@ -848,12 +850,12 @@ func initParamsKeeper(
func (app *NibiruApp) InitSimulationManager(
appCodec codec.Codec,
) {
//// create the simulation manager and define the order of the modules for deterministic simulations
////
//// NOTE: this is not required apps that don't use the simulator for fuzz testing
//// transactions
//epochsModule := epochs.NewAppModule(appCodec, app.EpochsKeeper)
//app.sm = module.NewSimulationManager(
// // create the simulation manager and define the order of the modules for deterministic simulations
// //
// // NOTE: this is not required apps that don't use the simulator for fuzz testing
// // transactions
// epochsModule := epochs.NewAppModule(appCodec, app.EpochsKeeper)
// app.sm = module.NewSimulationManager(
// auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
// bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
// feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
Expand All @@ -871,7 +873,7 @@ func (app *NibiruApp) InitSimulationManager(
// ibc.NewAppModule(app.ibcKeeper),
// ibctransfer.NewAppModule(app.transferKeeper),
// ibcfee.NewAppModule(app.ibcFeeKeeper),
//)
// )
//
//app.sm.RegisterStoreDecoders()
// app.sm.RegisterStoreDecoders()
}
12 changes: 12 additions & 0 deletions proto/nibiru/perp/v2/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ message GenesisState {

repeated nibiru.perp.v2.GenesisMarketLastVersion market_last_versions = 10
[ (gogoproto.nullable) = false ];

repeated GlobalVolume global_volumes = 13 [ (gogoproto.nullable) = false ];

repeated DNRAllocation rebates_allocations = 12 [ (gogoproto.nullable) = false ];

message GlobalVolume {
uint64 epoch = 1;
string volume = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
}

// GenesisMarketLastVersion is the last version including pair only used for
Expand Down
11 changes: 11 additions & 0 deletions proto/nibiru/perp/v2/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,14 @@ message ReserveSnapshot {
// milliseconds since unix epoch
int64 timestamp_ms = 2;
}

// DNRAllocation represents a rebates allocation for a given epoch.
message DNRAllocation {
// epoch defines the reference epoch for the allocation.
uint64 epoch = 1;
// amount of DNR allocated for the epoch.
repeated cosmos.base.v1beta1.Coin amount = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
35 changes: 35 additions & 0 deletions proto/nibiru/perp/v2/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ service Msg {

rpc ChangeCollateralDenom(MsgChangeCollateralDenom)
returns (MsgChangeCollateralDenomResponse) {}

rpc AllocateEpochRebates(MsgAllocateEpochRebates) returns (MsgAllocateEpochRebatesResponse) {}

rpc WithdrawEpochRebates(MsgWithdrawEpochRebates) returns (MsgWithdrawEpochRebatesResponse) {}
}

// -------------------------- Settle Position --------------------------
Expand Down Expand Up @@ -342,3 +346,34 @@ message MsgChangeCollateralDenom {
}

message MsgChangeCollateralDenomResponse {}

// -------------------------- AllocateEpochRebates --------------------------
message MsgAllocateEpochRebates {
string sender = 1;

// rebates to allocate
repeated cosmos.base.v1beta1.Coin rebates = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

message MsgAllocateEpochRebatesResponse {
repeated cosmos.base.v1beta1.Coin total_epoch_rebates = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// -------------------------- WithdrawEpochRebates --------------------------
message MsgWithdrawEpochRebates {
string sender = 1;
repeated uint64 epochs = 2;
}

message MsgWithdrawEpochRebatesResponse {
repeated cosmos.base.v1beta1.Coin withdrawn_rebates = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
87 changes: 87 additions & 0 deletions x/perp/v2/integration/action/dnr.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/NibiruChain/collections"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/x/common/testutil"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/x/common/asset"
"github.com/NibiruChain/nibiru/x/common/testutil/action"
Expand Down Expand Up @@ -212,3 +214,88 @@ func (s *setCustomDiscountAction) Do(app *app.NibiruApp, ctx sdk.Context) (outCt
app.PerpKeeperV2.TraderDiscounts.Insert(ctx, collections.Join(s.user, s.volume), s.fee)
return ctx, nil, true
}

type fundDnREpoch struct {
amt sdk.Coins
}

func (f fundDnREpoch) Do(app *app.NibiruApp, ctx sdk.Context) (outCtx sdk.Context, err error, isMandatory bool) {
tmpAcc := testutil.AccAddress()
ctx, err, _ = action.FundAccount(tmpAcc, f.amt).Do(app, ctx)
if err != nil {
return ctx, err, true
}
_, err = app.PerpKeeperV2.AllocateEpochRebates(ctx, tmpAcc, f.amt)
if err != nil {
return ctx, err, true
}
return ctx, nil, true
}

func FundDnREpoch(amt sdk.Coins) action.Action {
return fundDnREpoch{amt}
}

type startNewDnRepochAction struct {
}

func (s startNewDnRepochAction) Do(app *app.NibiruApp, ctx sdk.Context) (outCtx sdk.Context, err error, isMandatory bool) {
currentEpoch, err := app.PerpKeeperV2.DnREpoch.Get(ctx)
if err != nil {
return ctx, err, true
}
err = app.PerpKeeperV2.StartNewEpoch(ctx, currentEpoch+1)
if err != nil {
return ctx, err, true
}
return ctx, nil, true
}

func StartNewDnREpoch() action.Action {
return &startNewDnRepochAction{}
}

type dnrRebateIsAction struct {
user sdk.AccAddress
epoch uint64
expectedRewards sdk.Coins
}

func (d dnrRebateIsAction) Do(app *app.NibiruApp, ctx sdk.Context) (outCtx sdk.Context, err error, isMandatory bool) {
withdrawn, err := app.PerpKeeperV2.WithdrawEpochRebates(ctx, d.epoch, d.user)
if err != nil {
return ctx, err, true
}
if !withdrawn.IsEqual(d.expectedRewards) {
return ctx, fmt.Errorf("expected %s, got %s", d.expectedRewards, withdrawn), true
}
return ctx, nil, true
}

func DnRRebateIs(user sdk.AccAddress, epoch uint64, expectedRewards sdk.Coins) action.Action {
return &dnrRebateIsAction{
user: user,
epoch: epoch,
expectedRewards: expectedRewards,
}
}

func DnRRebateFails(user sdk.AccAddress, epoch uint64) action.Action {
return &dnrRebateFailsAction{
user: user,
epoch: epoch,
}
}

type dnrRebateFailsAction struct {
user sdk.AccAddress
epoch uint64
}

func (d dnrRebateFailsAction) Do(app *app.NibiruApp, ctx sdk.Context) (outCtx sdk.Context, err error, isMandatory bool) {
withdrawn, err := app.PerpKeeperV2.WithdrawEpochRebates(ctx, d.epoch, d.user)
if err == nil {
return ctx, fmt.Errorf("expected withdrawal error but got instead: %s rewards", withdrawn.String()), true
}
return ctx, nil, true
}
2 changes: 1 addition & 1 deletion x/perp/v2/keeper/clearing_house.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ func (k Keeper) transferFee(
return sdkmath.Int{}, err
}

exchangeFeeRatio, err = k.applyDiscountAndRebate(ctx, pair, trader, positionNotional, exchangeFeeRatio)
exchangeFeeRatio, err = k.calculateDiscount(ctx, pair, trader, positionNotional, exchangeFeeRatio)
if err != nil {
return sdkmath.Int{}, err
}
Expand Down
118 changes: 92 additions & 26 deletions x/perp/v2/keeper/dnr.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import (
"github.com/NibiruChain/collections"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/x/perp/v2/types"

"github.com/NibiruChain/nibiru/x/common/asset"
)

// DnRGCFrequency is the frequency at which the DnR garbage collector runs.
const DnRGCFrequency = 1000

// IntValueEncoder instructs collections on how to encode a math.Int as a value.
// TODO: move to collections.
var IntValueEncoder collections.ValueEncoder[math.Int] = intValueEncoder{}
Expand Down Expand Up @@ -69,32 +68,34 @@ func (intKeyEncoder) Decode(b []byte) (int, math.Int) {

func (intKeyEncoder) Stringify(key math.Int) string { return key.String() }

// StartNewEpoch is called by the epochs hooks when a new 30day epoch starts.
func (k Keeper) StartNewEpoch(ctx sdk.Context, identifier uint64) error {
// set the current epoch
k.DnREpoch.Set(ctx, identifier)
// we now check the rebates allocated for the previous epoch,
// and move them to the escrow such that they can be claimed lazily
// by users.
previousEpoch := identifier - 1
allocationAddr := k.AccountKeeper.GetModuleAddress(types.DNRAllocationModuleAccount)
allocationBalance := k.BankKeeper.GetAllBalances(ctx, allocationAddr)
err := k.BankKeeper.SendCoinsFromModuleToModule(ctx, types.DNRAllocationModuleAccount, types.DNREscrowModuleAccount, allocationBalance)
if err != nil {
return err
}

Check warning on line 84 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L83-L84

Added lines #L83 - L84 were not covered by tests
k.EpochRebateAllocations.Insert(ctx, previousEpoch, types.DNRAllocation{
Epoch: previousEpoch,
Amount: allocationBalance,
})
return nil
}

// IncreaseTraderVolume adds the volume to the user's volume for the current epoch.
// It also increases the global volume for the current epoch.
func (k Keeper) IncreaseTraderVolume(ctx sdk.Context, currentEpoch uint64, user sdk.AccAddress, volume math.Int) {
currentVolume := k.TraderVolumes.GetOr(ctx, collections.Join(user, currentEpoch), math.ZeroInt())
newVolume := currentVolume.Add(volume)
k.TraderVolumes.Insert(ctx, collections.Join(user, currentEpoch), newVolume)
k.gcUserVolume(ctx, user, currentEpoch)
}

// gcUserVolume deletes the un-needed user epochs.
func (k Keeper) gcUserVolume(ctx sdk.Context, user sdk.AccAddress, currentEpoch uint64) {
// we do not want to do this always.
if ctx.BlockHeight()%DnRGCFrequency != 0 {
return
}

rng := collections.PairRange[sdk.AccAddress, uint64]{}.
Prefix(user). // only iterate over the user's epochs.
EndExclusive(currentEpoch - 1). // we want to preserve current and last epoch, as it's needed to compute DnR rewards.
Descending()

for _, key := range k.TraderVolumes.Iterate(ctx, rng).Keys() {
err := k.TraderVolumes.Delete(ctx, key)
if err != nil {
panic(err)
}
}
k.GlobalVolumes.Insert(ctx, currentEpoch, k.GlobalVolumes.GetOr(ctx, currentEpoch, math.ZeroInt()).Add(volume))
}

// GetTraderVolumeLastEpoch returns the user's volume for the last epoch.
Expand Down Expand Up @@ -139,10 +140,10 @@ func (k Keeper) GetTraderDiscount(ctx sdk.Context, trader sdk.AccAddress, volume
return math.LegacyZeroDec(), false
}

// applyDiscountAndRebate applies the discount and rebate to the given exchange fee ratio.
// calculateDiscount applies the discount to the given exchange fee ratio.
// It updates the current epoch trader volume.
// It returns the new exchange fee ratio.
func (k Keeper) applyDiscountAndRebate(
func (k Keeper) calculateDiscount(
ctx sdk.Context,
_ asset.Pair,
trader sdk.AccAddress,
Expand Down Expand Up @@ -172,3 +173,68 @@ func (k Keeper) applyDiscountAndRebate(
// return discounted fee ratios
return discountedFeeRatio, nil
}

// WithdrawEpochRebates will withdraw the user's rebates for the given epoch.
func (k Keeper) WithdrawEpochRebates(ctx sdk.Context, epoch uint64, addr sdk.AccAddress) (withdrawn sdk.Coins, err error) {
// get the allocation for the epoch, this also ensures that if the user is trying to withdraw
// from current epoch the function immediately fails.
allocationCoins, err := k.EpochRebateAllocations.Get(ctx, epoch)
if err != nil {
return nil, err
}

Check warning on line 184 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L183-L184

Added lines #L183 - L184 were not covered by tests

// compute user weight
weight, err := k.computeUserWeight(ctx, addr, epoch)
if err != nil {
return nil, err
}

Check warning on line 190 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L189-L190

Added lines #L189 - L190 were not covered by tests
if weight.IsZero() {
return sdk.NewCoins(), nil
}

// calculate coins to distribute based on user weight
distrCoins := sdk.NewCoins()
for _, coin := range allocationCoins.Amount {
amt := coin.Amount.ToLegacyDec().Mul(weight).TruncateInt()
distrCoins = distrCoins.Add(sdk.NewCoin(coin.Denom, amt))
}

// send money to user from escrow only in case there's anything to distribute.
// this should never happen, since we're checking if the user has any weight.
if !distrCoins.IsZero() {
err = k.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.DNREscrowModuleAccount, addr, distrCoins)
if err != nil {
return nil, err
}

Check warning on line 208 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L207-L208

Added lines #L207 - L208 were not covered by tests
}

// garbage collect user volume. This ensures state is not bloated,
// and that the user cannot claim from the same allocation twice.
return distrCoins, k.TraderVolumes.Delete(ctx, collections.Join(addr, epoch))
}

// computeUserWeight computes the user's weight for the given epoch.
func (k Keeper) computeUserWeight(ctx sdk.Context, addr sdk.AccAddress, epoch uint64) (math.LegacyDec, error) {
// get user volume for the epoch
userVolume := k.TraderVolumes.GetOr(ctx, collections.Join(addr, epoch), math.ZeroInt())
if userVolume.IsZero() {
return math.LegacyZeroDec(), nil
}

// calculate the user's share
globalVolume, err := k.GlobalVolumes.Get(ctx, epoch)
if err != nil {
return math.LegacyDec{}, err
}

Check warning on line 228 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L227-L228

Added lines #L227 - L228 were not covered by tests
weight := userVolume.ToLegacyDec().Quo(globalVolume.ToLegacyDec())
return weight, nil
}

// AllocateEpochRebates will allocate the given amount of coins to the current epoch.
func (k Keeper) AllocateEpochRebates(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) (total sdk.Coins, err error) {
err = k.BankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.DNRAllocationModuleAccount, amount)
if err != nil {
return sdk.Coins{}, err
}

Check warning on line 238 in x/perp/v2/keeper/dnr.go

View check run for this annotation

Codecov / codecov/patch

x/perp/v2/keeper/dnr.go#L237-L238

Added lines #L237 - L238 were not covered by tests
return k.BankKeeper.GetAllBalances(ctx, k.AccountKeeper.GetModuleAddress(types.DNRAllocationModuleAccount)), nil
}
Loading

0 comments on commit c8c8c2e

Please sign in to comment.