Skip to content
4 changes: 1 addition & 3 deletions app/upgrades/v3/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ var (

// CreateGaugeFee is the fee required to create a new gauge.
expectCreateGaugeFee = DYM.Mul(sdk.NewInt(10))
// AddToGaugeFee is the fee required to add to gauge.
expectAddToGaugeFee = sdk.ZeroInt()

expectDelayedackEpochIdentifier = "hour"
expectDelayedackBridgingFee = sdk.NewDecWithPrec(1, 3)
Expand Down Expand Up @@ -103,7 +101,7 @@ func (s *UpgradeTestSuite) TestUpgrade() {
}

// Check Incentives parameters
if !incentivestypes.CreateGaugeFee.Equal(expectCreateGaugeFee) || !incentivestypes.AddToGaugeFee.Equal(expectAddToGaugeFee) {
if !incentivestypes.CreateGaugeFee.Equal(expectCreateGaugeFee) {
return fmt.Errorf("incentives parameters not set correctly")
}

Expand Down
17 changes: 16 additions & 1 deletion app/upgrades/v4/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/dymensionxyz/dymension/v3/app/upgrades"
delayedackkeeper "github.com/dymensionxyz/dymension/v3/x/delayedack/keeper"
delayedacktypes "github.com/dymensionxyz/dymension/v3/x/delayedack/types"
incentiveskeeper "github.com/dymensionxyz/dymension/v3/x/incentives/keeper"
rollappkeeper "github.com/dymensionxyz/dymension/v3/x/rollapp/keeper"
rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types"
sequencerkeeper "github.com/dymensionxyz/dymension/v3/x/sequencer/keeper"
Expand All @@ -50,7 +51,9 @@ func CreateUpgradeHandler(
}
migrateSequencers(ctx, keepers.SequencerKeeper)

// TODO: create rollapp gauges for each existing rollapp (https://github.com/dymensionxyz/dymension/issues/1005)
if err := migrateRollappGauges(ctx, keepers.RollappKeeper, keepers.IncentivesKeeper); err != nil {
return nil, err
}

// Start running the module migrations
logger.Debug("running module migrations ...")
Expand Down Expand Up @@ -112,6 +115,18 @@ func migrateRollappParams(ctx sdk.Context, rollappkeeper *rollappkeeper.Keeper)
rollappkeeper.SetParams(ctx, params)
}

// migrateRollappGauges creates a gauge for each rollapp in the store
func migrateRollappGauges(ctx sdk.Context, rollappkeeper *rollappkeeper.Keeper, incentivizeKeeper *incentiveskeeper.Keeper) error {
rollapps := rollappkeeper.GetAllRollapps(ctx)
for _, rollapp := range rollapps {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we not skip frozen rollapps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer not.
It doesn't really matter, as this gaguge won't be active for frozen rollapp, but it breaks the assumption of rollapp gauge for each rollapp

_, err := incentivizeKeeper.CreateRollappGauge(ctx, rollapp.RollappId)
if err != nil {
return err
}
}
return nil
}

func migrateRollapps(ctx sdk.Context, rollappkeeper *rollappkeeper.Keeper) error {
list := rollappkeeper.GetAllRollapps(ctx)
for _, oldRollapp := range list {
Expand Down
33 changes: 31 additions & 2 deletions app/upgrades/v4/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ func (s *UpgradeTestSuite) TestUpgrade() {
return
}

// TODO: check for rollapp gauges creation

// Check rollapp gauges
if err = s.validateRollappGaugesMigration(); err != nil {
return
}
return
},
expPass: true,
Expand Down Expand Up @@ -185,6 +187,33 @@ func (s *UpgradeTestSuite) validateRollappsMigration(numRoll int) error {
return nil
}

// validate rollapp gauges
func (s *UpgradeTestSuite) validateRollappGaugesMigration() error {
rollappMap := make(map[string]struct{}) // Create a map to store rollapps

rollapps := s.App.RollappKeeper.GetAllRollapps(s.Ctx)
for _, rollapp := range rollapps {
rollappMap[rollapp.RollappId] = struct{}{} // Store rollapp in the map
}

gauges := s.App.IncentivesKeeper.GetActiveGauges(s.Ctx)
if len(gauges) == len(rollapps) {
return fmt.Errorf("rollapp gauges not created for all rollapps")
}

for _, gauge := range gauges {
if gauge.GetRollapp() != nil {
rollappMap[gauge.GetRollapp().RollappId] = struct{}{}
}
}
// Check that for each rollapp there exists a rollapp gauge
if len(rollappMap) != len(rollapps) {
return fmt.Errorf("rollapp gauges not created for all rollapps")
}

return nil
}

func (s *UpgradeTestSuite) validateSequencersMigration(numSeq int) error {
testSeqs := s.seedSequencers(numSeq)
expectSequencers := make([]sequencertypes.Sequencer, len(testSeqs))
Expand Down
5 changes: 0 additions & 5 deletions x/incentives/keeper/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,3 @@ func (k Keeper) MoveUpcomingGaugeToActiveGauge(ctx sdk.Context, gauge types.Gaug
func (k Keeper) MoveActiveGaugeToFinishedGauge(ctx sdk.Context, gauge types.Gauge) error {
return k.moveActiveGaugeToFinishedGauge(ctx, gauge)
}

// ChargeFeeIfSufficientFeeDenomBalance see chargeFeeIfSufficientFeeDenomBalance spec.
func (k Keeper) ChargeFeeIfSufficientFeeDenomBalance(ctx sdk.Context, address sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) error {
return k.chargeFeeIfSufficientFeeDenomBalance(ctx, address, fee, gaugeCoins)
}
9 changes: 1 addition & 8 deletions x/incentives/keeper/gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,7 @@ func (k Keeper) CreateGauge(ctx sdk.Context, isPerpetual bool, owner sdk.AccAddr
return 0, fmt.Errorf("denom does not exist: %s", distrTo.Denom)
}

gauge := types.Gauge{
Id: k.GetLastGaugeID(ctx) + 1,
IsPerpetual: isPerpetual,
DistributeTo: &types.Gauge_Asset{Asset: &distrTo},
Coins: coins,
StartTime: startTime,
NumEpochsPaidOver: numEpochsPaidOver,
}
gauge := types.NewAssetGauge(k.GetLastGaugeID(ctx)+1, isPerpetual, distrTo, coins, startTime, numEpochsPaidOver)

if err := k.bk.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, gauge.Coins); err != nil {
return 0, err
Expand Down
9 changes: 1 addition & 8 deletions x/incentives/keeper/gauge_rollapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,7 @@ func (k Keeper) CreateRollappGauge(ctx sdk.Context, rollappId string) (uint64, e
return 0, fmt.Errorf("rollapp %s not found", rollappId)
}

gauge := types.Gauge{
Id: k.GetLastGaugeID(ctx) + 1,
IsPerpetual: true,
DistributeTo: &types.Gauge_Rollapp{
Rollapp: &types.RollappGauge{RollappId: rollappId},
},
NumEpochsPaidOver: 1,
}
gauge := types.NewRollappGauge(k.GetLastGaugeID(ctx)+1, rollappId)

err := k.setGauge(ctx, &gauge)
if err != nil {
Expand Down
97 changes: 0 additions & 97 deletions x/incentives/keeper/gauge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/stretchr/testify/suite"

apptesting "github.com/dymensionxyz/dymension/v3/app/apptesting"
"github.com/dymensionxyz/dymension/v3/x/incentives/types"
lockuptypes "github.com/osmosis-labs/osmosis/v15/x/lockup/types"

Expand Down Expand Up @@ -226,99 +225,3 @@ func (suite *KeeperTestSuite) TestGaugeOperations() {
}
}
}

func (suite *KeeperTestSuite) TestChargeFeeIfSufficientFeeDenomBalance() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

const baseFee = int64(100)

testcases := map[string]struct {
accountBalanceToFund sdk.Coin
feeToCharge int64
gaugeCoins sdk.Coins

expectError bool
}{
"fee + base denom gauge coin == acount balance, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee / 2,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(baseFee/2))),
},
"fee + base denom gauge coin < acount balance, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee/2 - 1,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(baseFee/2))),
},
"fee + base denom gauge coin > acount balance, error": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee/2 + 1,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(baseFee/2))),
expectError: true,
},
"fee + base denom gauge coin < acount balance, custom values, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(11793193112)),
feeToCharge: 55,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(328812))),
},
"account funded with coins other than base denom, error": {
accountBalanceToFund: sdk.NewCoin("usdc", sdk.NewInt(baseFee)),
feeToCharge: baseFee,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(baseFee/2))),
expectError: true,
},
"fee == account balance, no gauge coins, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee,
},
"gauge coins == account balance, no fee, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
gaugeCoins: sdk.NewCoins(sdk.NewCoin("adym", sdk.NewInt(baseFee))),
},
"fee == account balance, gauge coins in denom other than base, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("usdc", sdk.NewInt(baseFee*2))),
},
"fee + gauge coins == account balance, multiple gauge coins, one in denom other than base, success": {
accountBalanceToFund: sdk.NewCoin("adym", sdk.NewInt(baseFee)),
feeToCharge: baseFee / 2,
gaugeCoins: sdk.NewCoins(sdk.NewCoin("usdc", sdk.NewInt(baseFee*2)), sdk.NewCoin("adym", sdk.NewInt(baseFee/2))),
},
}

for name, tc := range testcases {
suite.Run(name, func() {
suite.SetupTest()

err := suite.App.TxFeesKeeper.SetBaseDenom(suite.Ctx, "adym")
suite.Require().NoError(err)

testAccount := apptesting.CreateRandomAccounts(1)[0]
ctx := suite.Ctx
incentivesKeepers := suite.App.IncentivesKeeper
bankKeeper := suite.App.BankKeeper

// Pre-fund account.
// suite.FundAcc(testAccount, testutil.DefaultAcctFunds)
suite.FundAcc(testAccount, sdk.NewCoins(tc.accountBalanceToFund))

oldBalanceAmount := bankKeeper.GetBalance(ctx, testAccount, "adym").Amount

// System under test.
err = incentivesKeepers.ChargeFeeIfSufficientFeeDenomBalance(ctx, testAccount, sdk.NewInt(tc.feeToCharge), tc.gaugeCoins)

// Assertions.
newBalanceAmount := bankKeeper.GetBalance(ctx, testAccount, "adym").Amount
if tc.expectError {
suite.Require().Error(err)

// check account balance unchanged
suite.Require().Equal(oldBalanceAmount, newBalanceAmount)
} else {
suite.Require().NoError(err)

// check account balance changed.
expectedNewBalanceAmount := oldBalanceAmount.Sub(sdk.NewInt(tc.feeToCharge))
suite.Require().Equal(expectedNewBalanceAmount.String(), newBalanceAmount.String())
}
})
}
}
51 changes: 18 additions & 33 deletions x/incentives/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package keeper

import (
"context"
"fmt"

errorsmod "cosmossdk.io/errors"

"github.com/dymensionxyz/dymension/v3/x/incentives/types"
"github.com/dymensionxyz/gerr-cosmos/gerrc"
"github.com/osmosis-labs/osmosis/v15/osmoutils"

errorsmod "cosmossdk.io/errors"
txfeestypes "github.com/osmosis-labs/osmosis/v15/x/txfees/types"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// msgServer provides a way to reference keeper pointer in the message server interface.
Expand All @@ -27,21 +30,24 @@ func NewMsgServerImpl(keeper *Keeper) types.MsgServer {
var _ types.MsgServer = msgServer{}

// CreateGauge creates a gauge and sends coins to the gauge.
// Creation fee is charged from the address and sent to the txfees module to be burned.
// Emits create gauge event and returns the create gauge response.
func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateGauge) (*types.MsgCreateGaugeResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
k := server.keeper
owner, err := sdk.AccAddressFromBech32(msg.Owner)
if err != nil {
return nil, err
}

if err := server.keeper.chargeFeeIfSufficientFeeDenomBalance(ctx, owner, types.CreateGaugeFee, msg.Coins); err != nil {
return nil, err
// charge creation fee
if err := k.chargeCreationFee(ctx, owner); err != nil {
return nil, fmt.Errorf("charge creation fee: %w", err)
}

gaugeID, err := server.keeper.CreateGauge(ctx, msg.IsPerpetual, owner, msg.Coins, msg.DistributeTo, msg.StartTime, msg.NumEpochsPaidOver)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
return nil, fmt.Errorf("create gauge: %w", err)
}

ctx.EventManager().EmitEvents(sdk.Events{
Expand All @@ -63,12 +69,9 @@ func (server msgServer) AddToGauge(goCtx context.Context, msg *types.MsgAddToGau
return nil, err
}

if err := server.keeper.chargeFeeIfSufficientFeeDenomBalance(ctx, owner, types.AddToGaugeFee, msg.Rewards); err != nil {
return nil, err
}
err = server.keeper.AddToGaugeRewards(ctx, owner, msg.Rewards, msg.GaugeId)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
return nil, fmt.Errorf("add to gauge rewards: %w", err)
}

ctx.EventManager().EmitEvents(sdk.Events{
Expand All @@ -81,30 +84,12 @@ func (server msgServer) AddToGauge(goCtx context.Context, msg *types.MsgAddToGau
return &types.MsgAddToGaugeResponse{}, nil
}

// chargeFeeIfSufficientFeeDenomBalance charges fee in the base denom on the address if the address has
// balance that is less than fee + amount of the coin from gaugeCoins that is of base denom.
// gaugeCoins might not have a coin of tx base denom. In that case, fee is only compared to balance.
// The fee is sent to the community pool.
func (k Keeper) chargeFeeIfSufficientFeeDenomBalance(ctx sdk.Context, address sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) (err error) {
var feeDenom string
if k.tk == nil {
feeDenom, err = sdk.GetBaseDenom()
} else {
feeDenom, err = k.tk.GetBaseDenom(ctx)
}
// chargeCreationFee charges the creation fee from the address.
// The fee is sent to the txfees module, to be burned.
func (k Keeper) chargeCreationFee(ctx sdk.Context, address sdk.AccAddress) (err error) {
feeDenom, err := k.tk.GetBaseDenom(ctx)
if err != nil {
return err
}

totalCost := gaugeCoins.AmountOf(feeDenom).Add(fee)
accountBalance := k.bk.GetBalance(ctx, address, feeDenom).Amount

if accountBalance.LT(totalCost) {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "account's balance is less than the total cost of the message. Balance: %s %s, Total Cost: %s", feeDenom, accountBalance, totalCost)
}

if err := k.ck.FundCommunityPool(ctx, sdk.NewCoins(sdk.NewCoin(feeDenom, fee)), address); err != nil {
return err
return errorsmod.Wrapf(gerrc.ErrInternal, "get base denom: %v", err)
}
return nil
return k.bk.SendCoinsFromAccountToModule(ctx, address, txfeestypes.ModuleName, sdk.NewCoins(sdk.NewCoin(feeDenom, types.CreateGaugeFee)))
Copy link
Contributor

Choose a reason for hiding this comment

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

what happens to these coins then? They stay in the module, should we burn them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the txfees module burns the accumulated tokens periodiocally

}
Loading
Loading