From cbfde1148a329dee2e1020748f8212d385c9e4cd Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 26 Jul 2022 12:28:06 -0400 Subject: [PATCH] feat(incentives)!: create gauge and add to gauge fee charge (#2227) * feat(incentives)!: create gauge and add to gauge fee charge * initialize txfees keeper before incentives * finish TestChargeFee * refactor to charge fee in message server * more tests * clean up * test balances * test create gauge fees (#2228) * test create gauge fees * add mod account to test * test create gauge fees * add mod account to test * move to msg server * merge * add comments * account keeper comment * fix TestCreateGaugeFee * apply appparams.BaseCoinUnit * remove txfees keeper from incentives and revert order * clean up * remove unused keepers fromm incentives keeper * Update x/incentives/keeper/gauge.go * Update x/incentives/keeper/gauge.go * clean up * fixture names * chargeFeeIfSufficientFeeDenomBalance test name * changelog * comment * finished tests (#2230) * finished tests * use keeper instead of math * remove unused tests * clean up * sim only allow accounts with enough to pay fee * lint * move fee to types and reuse in simulation (#2234) Co-authored-by: Adam Tucker Co-authored-by: Adam Tucker --- CHANGELOG.md | 1 + app/keepers/keepers.go | 1 + x/incentives/keeper/export_test.go | 9 +- x/incentives/keeper/gauge.go | 19 ++ x/incentives/keeper/gauge_test.go | 96 ++++++++++ x/incentives/keeper/keeper.go | 4 +- x/incentives/keeper/msg_server.go | 8 + x/incentives/keeper/msg_server_test.go | 243 +++++++++++++++++++++++++ x/incentives/simulation/operations.go | 6 +- x/incentives/types/expected_keepers.go | 6 + x/incentives/types/gauge.go | 7 + 11 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 x/incentives/keeper/msg_server_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f47dada78..95766a487b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#2016](https://github.com/osmosis-labs/osmosis/pull/2016) Add fixed 10000 gas cost for each Balancer swap * [#2147](https://github.com/osmosis-labs/osmosis/pull/2147) Set MaxAgeNumBlocks in v11 Upgrade Handler to two weeks. * [#2193](https://github.com/osmosis-labs/osmosis/pull/2193) Add TwapKeeper to the Osmosis app +* [#2227](https://github.com/osmosis-labs/osmosis/pull/2227) Enable charging fee in base denom for `CreateGauge` and `AddToGauge`. #### Golang API breaks diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index aeecb95a0e5..3369228ba26 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -267,6 +267,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.BankKeeper, appKeepers.LockupKeeper, appKeepers.EpochsKeeper, + appKeepers.DistrKeeper, ) appKeepers.SuperfluidKeeper = superfluidkeeper.NewKeeper( diff --git a/x/incentives/keeper/export_test.go b/x/incentives/keeper/export_test.go index ac465da4fe9..9eeb06ac191 100644 --- a/x/incentives/keeper/export_test.go +++ b/x/incentives/keeper/export_test.go @@ -7,8 +7,8 @@ import ( ) // AddGaugeRefByKey appends the provided gauge ID into an array associated with the provided key. -func (k Keeper) AddGaugeRefByKey(ctx sdk.Context, key []byte, guageID uint64) error { - return k.addGaugeRefByKey(ctx, key, guageID) +func (k Keeper) AddGaugeRefByKey(ctx sdk.Context, key []byte, gaugeID uint64) error { + return k.addGaugeRefByKey(ctx, key, gaugeID) } // DeleteGaugeRefByKey removes the provided gauge ID from an array associated with the provided key. @@ -35,3 +35,8 @@ 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) +} diff --git a/x/incentives/keeper/gauge.go b/x/incentives/keeper/gauge.go index 412ef436f69..8a8638bc243 100644 --- a/x/incentives/keeper/gauge.go +++ b/x/incentives/keeper/gauge.go @@ -7,9 +7,11 @@ import ( "strings" "time" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/gogo/protobuf/proto" db "github.com/tendermint/tm-db" + appparams "github.com/osmosis-labs/osmosis/v10/app/params" epochtypes "github.com/osmosis-labs/osmosis/v10/x/epochs/types" "github.com/osmosis-labs/osmosis/v10/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v10/x/lockup/types" @@ -278,3 +280,20 @@ func (k Keeper) GetEpochInfo(ctx sdk.Context) epochtypes.EpochInfo { params := k.GetParams(ctx) return k.ek.GetEpochInfo(ctx, params.DistrEpochIdentifier) } + +// 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. +// Returns nil on success, error otherwise. +func (k Keeper) chargeFeeIfSufficientFeeDenomBalance(ctx sdk.Context, address sdk.AccAddress, fee sdk.Int, gaugeCoins sdk.Coins) (err error) { + totalCost := gaugeCoins.AmountOf(appparams.BaseCoinUnit).Add(fee) + accountBalance := k.bk.GetBalance(ctx, address, appparams.BaseCoinUnit).Amount + if accountBalance.LT(totalCost) { + return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "account's balance of %s (%s) is less than the total cost of the message (%s)", appparams.BaseCoinUnit, accountBalance, totalCost) + } + if err := k.dk.FundCommunityPool(ctx, sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, fee)), address); err != nil { + return err + } + return nil +} diff --git a/x/incentives/keeper/gauge_test.go b/x/incentives/keeper/gauge_test.go index 45579973715..2e36c881846 100644 --- a/x/incentives/keeper/gauge_test.go +++ b/x/incentives/keeper/gauge_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/suite" + appparams "github.com/osmosis-labs/osmosis/v10/app/params" "github.com/osmosis-labs/osmosis/v10/x/incentives/types" lockuptypes "github.com/osmosis-labs/osmosis/v10/x/lockup/types" @@ -237,3 +238,98 @@ func (suite *KeeperTestSuite) TestGaugeOperations() { } } } + +func (suite *KeeperTestSuite) TestChargeFeeIfSufficientFeeDenomBalance() { + 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(appparams.DefaultBondDenom, sdk.NewInt(baseFee)), + feeToCharge: baseFee / 2, + gaugeCoins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee/2))), + }, + "fee + base denom gauge coin < acount balance, success": { + accountBalanceToFund: sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee)), + feeToCharge: baseFee/2 - 1, + gaugeCoins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee/2))), + }, + "fee + base denom gauge coin > acount balance, error": { + accountBalanceToFund: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(baseFee)), + feeToCharge: baseFee/2 + 1, + gaugeCoins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee/2))), + + expectError: true, + }, + "fee + base denom gauge coin < acount balance, custom values, success": { + accountBalanceToFund: sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(11793193112)), + feeToCharge: 55, + gaugeCoins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, 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(appparams.DefaultBondDenom, sdk.NewInt(baseFee/2))), + + expectError: true, + }, + "fee == account balance, no gauge coins, success": { + accountBalanceToFund: sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee)), + feeToCharge: baseFee, + }, + "gauge coins == account balance, no fee, success": { + accountBalanceToFund: sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee)), + gaugeCoins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee))), + }, + "fee == account balance, gauge coins in denom other than base, success": { + accountBalanceToFund: sdk.NewCoin(appparams.DefaultBondDenom, 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(appparams.DefaultBondDenom, sdk.NewInt(baseFee)), + feeToCharge: baseFee / 2, + gaugeCoins: sdk.NewCoins(sdk.NewCoin("usdc", sdk.NewInt(baseFee*2)), sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(baseFee/2))), + }, + } + + for name, tc := range testcases { + suite.Run(name, func() { + suite.SetupTest() + + testAccount := suite.TestAccs[0] + + ctx := suite.Ctx + incentivesKeepers := suite.App.IncentivesKeeper + bankKeeper := suite.App.BankKeeper + + // Pre-fund account. + suite.FundAcc(testAccount, sdk.NewCoins(tc.accountBalanceToFund)) + + oldBalanceAmount := bankKeeper.GetBalance(ctx, testAccount, appparams.DefaultBondDenom).Amount + + // System under test. + err := incentivesKeepers.ChargeFeeIfSufficientFeeDenomBalance(ctx, testAccount, sdk.NewInt(tc.feeToCharge), tc.gaugeCoins) + + // Assertions. + newBalanceAmount := bankKeeper.GetBalance(ctx, testAccount, appparams.DefaultBondDenom).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()) + } + }) + } +} diff --git a/x/incentives/keeper/keeper.go b/x/incentives/keeper/keeper.go index 09c80d185a5..d21abad3579 100644 --- a/x/incentives/keeper/keeper.go +++ b/x/incentives/keeper/keeper.go @@ -22,10 +22,11 @@ type Keeper struct { bk types.BankKeeper lk types.LockupKeeper ek types.EpochKeeper + dk types.DistrKeeper } // NewKeeper returns a new instance of the incentive module keeper struct. -func NewKeeper(cdc codec.Codec, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, bk types.BankKeeper, lk types.LockupKeeper, ek types.EpochKeeper) *Keeper { +func NewKeeper(cdc codec.Codec, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, bk types.BankKeeper, lk types.LockupKeeper, ek types.EpochKeeper, dk types.DistrKeeper) *Keeper { if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) } @@ -37,6 +38,7 @@ func NewKeeper(cdc codec.Codec, storeKey sdk.StoreKey, paramSpace paramtypes.Sub bk: bk, lk: lk, ek: ek, + dk: dk, } } diff --git a/x/incentives/keeper/msg_server.go b/x/incentives/keeper/msg_server.go index bfcffaf99e0..c966ab5f4bb 100644 --- a/x/incentives/keeper/msg_server.go +++ b/x/incentives/keeper/msg_server.go @@ -33,6 +33,10 @@ func (server msgServer) CreateGauge(goCtx context.Context, msg *types.MsgCreateG return nil, err } + if err := server.keeper.chargeFeeIfSufficientFeeDenomBalance(ctx, owner, types.CreateGaugeFee, msg.Coins); err != nil { + return nil, 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()) @@ -56,6 +60,10 @@ func (server msgServer) AddToGauge(goCtx context.Context, msg *types.MsgAddToGau if err != nil { 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()) diff --git a/x/incentives/keeper/msg_server_test.go b/x/incentives/keeper/msg_server_test.go new file mode 100644 index 00000000000..497a5d2d92b --- /dev/null +++ b/x/incentives/keeper/msg_server_test.go @@ -0,0 +1,243 @@ +package keeper_test + +import ( + "time" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + appparams "github.com/osmosis-labs/osmosis/v10/app/params" + "github.com/osmosis-labs/osmosis/v10/x/incentives/keeper" + "github.com/osmosis-labs/osmosis/v10/x/incentives/types" + lockuptypes "github.com/osmosis-labs/osmosis/v10/x/lockup/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ = suite.TestingSuite(nil) + +func (suite *KeeperTestSuite) TestCreateGauge_Fee() { + tests := []struct { + name string + accountBalanceToFund sdk.Coins + gaugeAddition sdk.Coins + expectedEndBalance sdk.Coins + isPerpetual bool + isModuleAccount bool + expectErr bool + }{ + { + name: "user creates a non-perpetual gauge and fills gauge with all remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(60000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "user creates a non-perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "user with multiple denoms creates a non-perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "module account creates a perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + isPerpetual: true, + isModuleAccount: true, + }, + { + name: "user with multiple denoms creates a perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + isPerpetual: true, + }, + { + name: "user tries to create a non-perpetual gauge but does not have enough funds to pay for the create gauge fee", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(40000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + expectErr: true, + }, + { + name: "user tries to create a non-perpetual gauge but does not have the correct fee denom", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(60000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000000))), + expectErr: true, + }, + { + name: "one user tries to create a gauge, has enough funds to pay for the create gauge fee but not enough to fill the gauge", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(60000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(30000000))), + expectErr: true, + }, + } + + for _, tc := range tests { + suite.SetupTest() + + testAccountPubkey := secp256k1.GenPrivKeyFromSecret([]byte("acc")).PubKey() + testAccountAddress := sdk.AccAddress(testAccountPubkey.Address()) + + ctx := suite.Ctx + bankKeeper := suite.App.BankKeeper + accountKeeper := suite.App.AccountKeeper + msgServer := keeper.NewMsgServerImpl(suite.App.IncentivesKeeper) + + suite.FundAcc(testAccountAddress, tc.accountBalanceToFund) + + if tc.isModuleAccount { + modAcc := authtypes.NewModuleAccount(authtypes.NewBaseAccount(testAccountAddress, testAccountPubkey, 1, 0), + "module", + "permission", + ) + accountKeeper.SetModuleAccount(ctx, modAcc) + } + + suite.SetupManyLocks(1, defaultLiquidTokens, defaultLPTokens, defaultLockDuration) + distrTo := lockuptypes.QueryCondition{ + LockQueryType: lockuptypes.ByDuration, + Denom: defaultLPDenom, + Duration: defaultLockDuration, + } + + msg := &types.MsgCreateGauge{ + IsPerpetual: tc.isPerpetual, + Owner: testAccountAddress.String(), + DistributeTo: distrTo, + Coins: tc.gaugeAddition, + StartTime: time.Now(), + NumEpochsPaidOver: 1, + } + // System under test. + _, err := msgServer.CreateGauge(sdk.WrapSDKContext(ctx), msg) + + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + + balanceAmount := bankKeeper.GetAllBalances(ctx, testAccountAddress) + + if tc.expectErr { + suite.Require().Equal(tc.accountBalanceToFund.String(), balanceAmount.String(), "test: %v", tc.name) + } else { + fee := sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, types.CreateGaugeFee)) + accountBalance := tc.accountBalanceToFund.Sub(tc.gaugeAddition) + finalAccountBalance := accountBalance.Sub(fee) + suite.Require().Equal(finalAccountBalance.String(), balanceAmount.String(), "test: %v", tc.name) + } + } +} + +func (suite *KeeperTestSuite) TestAddToGauge_Fee() { + + tests := []struct { + name string + accountBalanceToFund sdk.Coins + gaugeAddition sdk.Coins + nonexistentGauge bool + isPerpetual bool + isModuleAccount bool + expectErr bool + }{ + { + name: "user creates a non-perpetual gauge and fills gauge with all remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(35000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "user creates a non-perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "user with multiple denoms creates a non-perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + }, + { + name: "module account creates a perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + isPerpetual: true, + isModuleAccount: true, + }, + { + name: "user with multiple denoms creates a perpetual gauge and fills gauge with some remaining tokens", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(70000000)), sdk.NewCoin("foo", sdk.NewInt(70000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + isPerpetual: true, + }, + { + name: "user tries to create a non-perpetual gauge but does not have enough funds to pay for the create gauge fee", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(20000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(10000000))), + expectErr: true, + }, + { + name: "user tries to add to a non-perpetual gauge but does not have the correct fee denom", + accountBalanceToFund: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(60000000))), + gaugeAddition: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000000))), + expectErr: true, + }, + } + + for _, tc := range tests { + suite.SetupTest() + + testAccountPubkey := secp256k1.GenPrivKeyFromSecret([]byte("acc")).PubKey() + testAccountAddress := sdk.AccAddress(testAccountPubkey.Address()) + + ctx := suite.Ctx + bankKeeper := suite.App.BankKeeper + incentivesKeeper := suite.App.IncentivesKeeper + accountKeeper := suite.App.AccountKeeper + msgServer := keeper.NewMsgServerImpl(incentivesKeeper) + + suite.FundAcc(testAccountAddress, tc.accountBalanceToFund) + + if tc.isModuleAccount { + modAcc := authtypes.NewModuleAccount(authtypes.NewBaseAccount(testAccountAddress, testAccountPubkey, 1, 0), + "module", + "permission", + ) + accountKeeper.SetModuleAccount(ctx, modAcc) + } + + // System under test. + coins := sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, sdk.NewInt(500000000))) + gaugeID, _, _, _ := suite.SetupNewGauge(true, coins) + if tc.nonexistentGauge { + gaugeID = incentivesKeeper.GetLastGaugeID(ctx) + 1 + } + msg := &types.MsgAddToGauge{ + Owner: testAccountAddress.String(), + GaugeId: gaugeID, + Rewards: tc.gaugeAddition, + } + + _, err := msgServer.AddToGauge(sdk.WrapSDKContext(ctx), msg) + + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + + bal := bankKeeper.GetAllBalances(ctx, testAccountAddress) + + if tc.expectErr { + suite.Require().Equal(tc.accountBalanceToFund.String(), bal.String(), "test: %v", tc.name) + } else { + fee := sdk.NewCoins(sdk.NewCoin(appparams.BaseCoinUnit, types.AddToGaugeFee)) + accountBalance := tc.accountBalanceToFund.Sub(tc.gaugeAddition) + finalAccountBalance := accountBalance.Sub(fee) + suite.Require().Equal(finalAccountBalance.String(), bal.String(), "test: %v", tc.name) + } + } +} diff --git a/x/incentives/simulation/operations.go b/x/incentives/simulation/operations.go index 10b281f4a09..81d4e0854c7 100644 --- a/x/incentives/simulation/operations.go +++ b/x/incentives/simulation/operations.go @@ -18,6 +18,8 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + appparams "github.com/osmosis-labs/osmosis/v10/app/params" ) // Simulation operation weights constants. @@ -115,7 +117,7 @@ func SimulateMsgCreateGauge(ak stakingTypes.AccountKeeper, bk stakingTypes.BankK ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { simAccount, _ := simtypes.RandomAcc(r, accs) simCoins := bk.SpendableCoins(ctx, simAccount.Address) - if simCoins.Len() <= 0 { + if simCoins.Len() <= 0 || simCoins.AmountOf(appparams.BaseCoinUnit).LT(types.CreateGaugeFee) { return simtypes.NoOpMsg( types.ModuleName, types.TypeMsgCreateGauge, "Account have no coin"), nil, nil } @@ -154,7 +156,7 @@ func SimulateMsgAddToGauge(ak stakingTypes.AccountKeeper, bk stakingTypes.BankKe ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { simAccount, _ := simtypes.RandomAcc(r, accs) simCoins := bk.SpendableCoins(ctx, simAccount.Address) - if simCoins.Len() <= 0 { + if simCoins.Len() <= 0 || simCoins.AmountOf(appparams.BaseCoinUnit).LT(types.AddToGaugeFee) { return simtypes.NoOpMsg( types.ModuleName, types.TypeMsgAddToGauge, "Account have no coin"), nil, nil } diff --git a/x/incentives/types/expected_keepers.go b/x/incentives/types/expected_keepers.go index e377908df79..5319039c3fd 100644 --- a/x/incentives/types/expected_keepers.go +++ b/x/incentives/types/expected_keepers.go @@ -12,6 +12,7 @@ import ( // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin HasSupply(ctx sdk.Context, denom string) bool @@ -36,3 +37,8 @@ type LockupKeeper interface { type EpochKeeper interface { GetEpochInfo(ctx sdk.Context, identifier string) epochstypes.EpochInfo } + +// DistrKeeper defines the contract needed to be fulfilled for distribution keeper. +type DistrKeeper interface { + FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error +} diff --git a/x/incentives/types/gauge.go b/x/incentives/types/gauge.go index bb0f97d087c..ec3b112d31f 100644 --- a/x/incentives/types/gauge.go +++ b/x/incentives/types/gauge.go @@ -8,6 +8,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var ( + // CreateGaugeFee is the fee required to create a new gauge. + CreateGaugeFee = sdk.NewInt(50 * 1_000_000) + // AddToGagugeFee is the fee required to add to gauge. + AddToGaugeFee = sdk.NewInt(25 * 1_000_000) +) + // NewGauge creates a new gauge struct given the required gauge parameters. func NewGauge(id uint64, isPerpetual bool, distrTo lockuptypes.QueryCondition, coins sdk.Coins, startTime time.Time, numEpochsPaidOver uint64, filledEpochs uint64, distrCoins sdk.Coins) Gauge { return Gauge{