diff --git a/app/app.go b/app/app.go index 661db9c67..e8901b035 100644 --- a/app/app.go +++ b/app/app.go @@ -808,8 +808,6 @@ func NewElysApp( &app.CommitmentKeeper, app.AssetprofileKeeper, ) - stablestake := stablestake.NewAppModule(appCodec, app.StablestakeKeeper, app.AccountKeeper, app.BankKeeper) - app.CommitmentKeeper = *commitmentKeeper.SetHooks( commitmentmodulekeeper.NewMultiCommitmentHooks( app.IncentiveKeeper.CommitmentHooks(), @@ -846,6 +844,11 @@ func NewElysApp( ) masterchefModule := masterchefmodule.NewAppModule(appCodec, app.MasterchefKeeper, app.AccountKeeper, app.BankKeeper) + app.StablestakeKeeper = *app.StablestakeKeeper.SetHooks(stablestakekeeper.NewMultiStableStakeHooks( + app.MasterchefKeeper.StableStakeHooks(), + )) + stablestakeModule := stablestake.NewAppModule(appCodec, app.StablestakeKeeper, app.AccountKeeper, app.BankKeeper) + app.IncentiveKeeper = *incentivemodulekeeper.NewKeeper( appCodec, keys[incentivemoduletypes.StoreKey], @@ -1141,7 +1144,7 @@ func NewElysApp( accountedPoolModule, transferhookModule, clockModule, - stablestake, + stablestakeModule, leveragelpModule, masterchefModule, estakingModule, diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 9b448ebc6..3652f74d1 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -5,7 +5,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" m "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + estakingtypes "github.com/elys-network/elys/x/estaking/types" + mastercheftypes "github.com/elys-network/elys/x/masterchef/types" ) func SetupHandlers(app *ElysApp) { @@ -56,7 +59,7 @@ func loadUpgradeStore(app *ElysApp) { if shouldLoadUpgradeStore(app, upgradeInfo) { storeUpgrades := storetypes.StoreUpgrades{ - // Added: []string{}, + Added: []string{distrtypes.StoreKey, mastercheftypes.StoreKey, estakingtypes.StoreKey}, } // Use upgrade store loader for the initial loading of all stores when app starts, // it checks if version == upgradeHeight and applies store upgrades before loading the stores, diff --git a/x/amm/types/utils.go b/x/amm/types/utils.go index ef6b32988..51258dc01 100644 --- a/x/amm/types/utils.go +++ b/x/amm/types/utils.go @@ -2,6 +2,8 @@ package types import ( fmt "fmt" + "strconv" + "strings" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,6 +13,14 @@ func GetPoolShareDenom(poolId uint64) string { return fmt.Sprintf("amm/pool/%d", poolId) } +func GetPoolIdFromShareDenom(shareDenom string) (uint64, error) { + poolId, err := strconv.Atoi(strings.TrimPrefix(shareDenom, "amm/pool/")) + if err != nil { + return 0, err + } + return uint64(poolId), nil +} + // poolAssetsCoins returns all the coins corresponding to a slice of pool assets. func poolAssetsCoins(assets []PoolAsset) sdk.Coins { coins := sdk.Coins{} diff --git a/x/incentive/migrations/v11_migration.go b/x/incentive/migrations/v11_migration.go index 5f6847692..08a800e62 100644 --- a/x/incentive/migrations/v11_migration.go +++ b/x/incentive/migrations/v11_migration.go @@ -4,10 +4,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + ammtypes "github.com/elys-network/elys/x/amm/types" commitmenttypes "github.com/elys-network/elys/x/commitment/types" estakingtypes "github.com/elys-network/elys/x/estaking/types" mastercheftypes "github.com/elys-network/elys/x/masterchef/types" ptypes "github.com/elys-network/elys/x/parameter/types" + stablestaketypes "github.com/elys-network/elys/x/stablestake/types" ) func (m Migrator) V11Migration(ctx sdk.Context) error { @@ -30,7 +32,7 @@ func (m Migrator) V11Migration(ctx sdk.Context) error { // initiate masterchef params m.masterchefKeeper.SetParams(ctx, mastercheftypes.NewParams( - nil, // TODO: + nil, sdk.NewDecWithPrec(60, 2), sdk.NewDecWithPrec(25, 2), mastercheftypes.DexRewardsTracker{ @@ -44,7 +46,7 @@ func (m Migrator) V11Migration(ctx sdk.Context) error { // initiate estaking module data m.estakingKeeper.InitGenesis(ctx, estakingtypes.GenesisState{ Params: estakingtypes.Params{ - StakeIncentives: nil, // TODO: + StakeIncentives: nil, EdenCommitVal: "", EdenbCommitVal: "", MaxEdenRewardAprStakers: sdk.NewDecWithPrec(3, 1), // 30% @@ -103,21 +105,32 @@ func (m Migrator) V11Migration(ctx sdk.Context) error { VestingTokens: legacy.VestingTokens, } m.commitmentKeeper.SetCommitments(ctx, commitments) + commParams := m.commitmentKeeper.GetParams(ctx) for _, committed := range commitments.CommittedTokens { - if committed.Denom == ptypes.Eden { + if committed.Denom == ptypes.Eden && commParams.TotalCommitted.AmountOf(ptypes.Eden).IsPositive() { err = m.estakingKeeper.Hooks().AfterDelegationModified(ctx, addr, edenValAddr) if err != nil { panic(err) } } - if committed.Denom == ptypes.EdenB { + if committed.Denom == ptypes.EdenB && commParams.TotalCommitted.AmountOf(ptypes.EdenB).IsPositive() { err = m.estakingKeeper.Hooks().AfterDelegationModified(ctx, addr, edenBValAddr) if err != nil { panic(err) } } - } + // Execute hook for normal amm pool deposit + poolId, err := ammtypes.GetPoolIdFromShareDenom(committed.Denom) + if err == nil { + m.masterchefKeeper.AfterDeposit(ctx, poolId, addr.String(), committed.Amount) + } + + // Execute hook for stablestake deposit + if committed.Denom == stablestaketypes.GetShareDenom() { + m.masterchefKeeper.AfterDeposit(ctx, stablestaketypes.PoolId, addr.String(), committed.Amount) + } + } } return nil diff --git a/x/masterchef/keeper/abci.go b/x/masterchef/keeper/abci.go index 904e54c49..36f03a0f8 100644 --- a/x/masterchef/keeper/abci.go +++ b/x/masterchef/keeper/abci.go @@ -4,6 +4,7 @@ import ( "errors" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ammtypes "github.com/elys-network/elys/x/amm/types" @@ -21,6 +22,26 @@ func (k Keeper) EndBlocker(ctx sdk.Context) { k.ProcessExternalRewardsDistribution(ctx) } +func (k Keeper) GetPoolTVL(ctx sdk.Context, poolId uint64) math.LegacyDec { + if poolId == stabletypes.PoolId { + entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency) + if !found { + return math.LegacyZeroDec() + } + baseCurrency := entry.Denom + return k.stableKeeper.TVL(ctx, k.oracleKeeper, baseCurrency) + } + ammPool, found := k.amm.GetPool(ctx, poolId) + if found { + tvl, err := ammPool.TVL(ctx, k.oracleKeeper) + if err != nil { + return math.LegacyZeroDec() + } + return tvl + } + return math.LegacyZeroDec() +} + func (k Keeper) ProcessExternalRewardsDistribution(ctx sdk.Context) { canDistribute := k.CanDistributeLPRewards(ctx) if !canDistribute { @@ -55,25 +76,21 @@ func (k Keeper) ProcessExternalRewardsDistribution(ctx sdk.Context) { k.SetPool(ctx, pool) } - ammPool, found := k.amm.GetPool(ctx, pool.PoolId) - if found { - tvl, err := ammPool.TVL(ctx, k.oracleKeeper) - if err == nil { - yearlyIncentiveRewardsTotal := externalIncentive.AmountPerBlock. - Mul(lpIncentive.TotalBlocksPerYear). - Quo(pool.NumBlocks) - - entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency) - if found { - baseCurrency := entry.Denom - pool.ExternalIncentiveApr = sdk.NewDecFromInt(yearlyIncentiveRewardsTotal). - Mul(k.amm.GetTokenPrice(ctx, externalIncentive.RewardDenom, baseCurrency)). - Quo(tvl) - k.SetPool(ctx, pool) - } + tvl := k.GetPoolTVL(ctx, pool.PoolId) + if tvl.IsPositive() { + yearlyIncentiveRewardsTotal := externalIncentive.AmountPerBlock. + Mul(lpIncentive.TotalBlocksPerYear). + Quo(pool.NumBlocks) + + entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency) + if found { + baseCurrency := entry.Denom + pool.ExternalIncentiveApr = sdk.NewDecFromInt(yearlyIncentiveRewardsTotal). + Mul(k.amm.GetTokenPrice(ctx, externalIncentive.RewardDenom, baseCurrency)). + Quo(tvl) + k.SetPool(ctx, pool) } } - } if curBlockHeight.Uint64() == externalIncentive.ToBlock { @@ -229,31 +246,13 @@ func (k Keeper) UpdateLPRewards(ctx sdk.Context) error { return errorsmod.Wrap(types.ErrNoInflationaryParams, "invalid eden price") } - stableStakePoolId := uint64(stabletypes.PoolId) // Distribute Eden / USDC Rewards for _, pool := range k.GetAllPools(ctx) { - var proxyTVL, tvl sdk.Dec var err error - if pool.PoolId == stableStakePoolId { - tvl = k.stableKeeper.TVL(ctx, k.oracleKeeper, baseCurrency) - proxyTVL = tvl.Mul(pool.Multiplier) - } else { - ammPool, found := k.amm.GetPool(ctx, pool.PoolId) - if !found { - continue - } - - // ------------ New Eden calculation ------------------- - // ----------------------------------------------------- - // newEdenAllocated = 80 / ( 80 + 90 + 200 + 0) * 100 - // Pool share = 80 - // edenAmountLp = 100 - tvl, err = ammPool.TVL(ctx, k.oracleKeeper) - if err != nil { - continue - } - // Calculate Proxy TVL share considering multiplier - proxyTVL = tvl.Mul(pool.Multiplier) + tvl := k.GetPoolTVL(ctx, pool.PoolId) + proxyTVL := tvl.Mul(pool.Multiplier) + if proxyTVL.IsZero() { + continue } poolShare := sdk.ZeroDec() @@ -487,37 +486,14 @@ func (k Keeper) CollectDEXRevenue(ctx sdk.Context) (sdk.Coins, sdk.DecCoins) { func (k Keeper) CalculateProxyTVL(ctx sdk.Context, baseCurrency string) sdk.Dec { multipliedShareSum := sdk.ZeroDec() stableStakePoolId := uint64(stabletypes.PoolId) + _, found := k.GetPool(ctx, stableStakePoolId) + // Ensure stablestakePoolParams exist + if !found { + k.InitStableStakePoolParams(ctx, stableStakePoolId) + } for _, pool := range k.GetAllPools(ctx) { - if pool.PoolId == stableStakePoolId { - // Get pool info from incentive param - poolInfo, found := k.GetPool(ctx, stableStakePoolId) - if !found { - k.InitStableStakePoolParams(ctx, stableStakePoolId) - poolInfo, _ = k.GetPool(ctx, stableStakePoolId) - } - tvl := k.stableKeeper.TVL(ctx, k.oracleKeeper, baseCurrency) - proxyTVL := tvl.Mul(poolInfo.Multiplier) - multipliedShareSum = multipliedShareSum.Add(proxyTVL) - continue - } - - ammPool, found := k.amm.GetPool(ctx, pool.PoolId) - if !found { - continue - } - - tvl, err := ammPool.TVL(ctx, k.oracleKeeper) - if err != nil { - continue - } - - // Get pool info from incentive param - poolInfo, found := k.GetPool(ctx, ammPool.GetPoolId()) - if !found { - continue - } - - proxyTVL := tvl.Mul(poolInfo.Multiplier) + tvl := k.GetPoolTVL(ctx, pool.PoolId) + proxyTVL := tvl.Mul(pool.Multiplier) // Calculate total pool share by TVL and multiplier multipliedShareSum = multipliedShareSum.Add(proxyTVL) diff --git a/x/masterchef/keeper/apr_pool.go b/x/masterchef/keeper/apr_pool.go index 014127548..c28a1a23c 100644 --- a/x/masterchef/keeper/apr_pool.go +++ b/x/masterchef/keeper/apr_pool.go @@ -7,7 +7,7 @@ import ( func (k Keeper) CalculatePoolAprs(ctx sdk.Context, ids []uint64) []types.PoolApr { if len(ids) == 0 { - pools := k.amm.GetAllPool(ctx) + pools := k.GetAllPools(ctx) for _, pool := range pools { ids = append(ids, pool.PoolId) } diff --git a/x/masterchef/keeper/hooks_masterchef.go b/x/masterchef/keeper/hooks_masterchef.go index 47a11c12d..75dd055a4 100644 --- a/x/masterchef/keeper/hooks_masterchef.go +++ b/x/masterchef/keeper/hooks_masterchef.go @@ -5,9 +5,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ammtypes "github.com/elys-network/elys/x/amm/types" "github.com/elys-network/elys/x/masterchef/types" + stablestaketypes "github.com/elys-network/elys/x/stablestake/types" ) func (k Keeper) GetPoolTotalSupply(ctx sdk.Context, poolId uint64) sdk.Int { + if poolId == stablestaketypes.PoolId { + params := k.cmk.GetParams(ctx) + return params.TotalCommitted.AmountOf(stablestaketypes.GetShareDenom()) + } + pool, found := k.amm.GetPool(ctx, poolId) if !found { return sdk.ZeroInt() @@ -18,8 +24,12 @@ func (k Keeper) GetPoolTotalSupply(ctx sdk.Context, poolId uint64) sdk.Int { func (k Keeper) GetPoolBalance(ctx sdk.Context, poolId uint64, user string) sdk.Int { commitments := k.cmk.GetCommitments(ctx, user) + shareDenom := stablestaketypes.GetShareDenom() + if poolId != stablestaketypes.PoolId { + shareDenom = ammtypes.GetPoolShareDenom(poolId) + } - return commitments.GetCommittedAmountForDenom(ammtypes.GetPoolShareDenom(poolId)) + return commitments.GetCommittedAmountForDenom(shareDenom) } func (k Keeper) UpdateAccPerShare(ctx sdk.Context, poolId uint64, rewardDenom string, amount sdk.Int) { @@ -34,11 +44,15 @@ func (k Keeper) UpdateAccPerShare(ctx sdk.Context, poolId uint64, rewardDenom st } } + supply := k.GetPoolTotalSupply(ctx, poolId) + if supply.IsZero() { + return + } poolRewardInfo.PoolAccRewardPerShare = poolRewardInfo.PoolAccRewardPerShare.Add( - math.LegacyNewDecFromInt(amount.Mul(ammtypes.OneShare)).Quo(math.LegacyNewDecFromInt(k.GetPoolTotalSupply(ctx, poolId))), + math.LegacyNewDecFromInt(amount.Mul(ammtypes.OneShare)). + Quo(math.LegacyNewDecFromInt(supply)), ) poolRewardInfo.LastUpdatedBlock = uint64(ctx.BlockHeight()) - k.SetPoolRewardInfo(ctx, poolRewardInfo) } diff --git a/x/masterchef/keeper/hooks_stablestake.go b/x/masterchef/keeper/hooks_stablestake.go new file mode 100644 index 000000000..e4283126c --- /dev/null +++ b/x/masterchef/keeper/hooks_stablestake.go @@ -0,0 +1,29 @@ +package keeper + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stablestaketypes "github.com/elys-network/elys/x/stablestake/types" +) + +// Hooks wrapper struct for incentive keeper +type StableStakeHooks struct { + k Keeper +} + +var _ stablestaketypes.StableStakeHooks = StableStakeHooks{} + +// Return the wrapper struct +func (k Keeper) StableStakeHooks() StableStakeHooks { + return StableStakeHooks{k} +} + +func (h StableStakeHooks) AfterBond(ctx sdk.Context, sender string, shareAmount math.Int) error { + h.k.AfterDeposit(ctx, stablestaketypes.PoolId, sender, shareAmount) + return nil +} + +func (h StableStakeHooks) AfterUnbond(ctx sdk.Context, sender string, shareAmount math.Int) error { + h.k.AfterWithdraw(ctx, stablestaketypes.PoolId, sender, shareAmount) + return nil +} diff --git a/x/masterchef/keeper/query.go b/x/masterchef/keeper/query.go index fb24cf9ad..4970740e7 100644 --- a/x/masterchef/keeper/query.go +++ b/x/masterchef/keeper/query.go @@ -49,7 +49,7 @@ func (k Keeper) UserRewardInfo(goCtx context.Context, req *types.QueryUserReward userRewardInfo, found := k.GetUserRewardInfo(ctx, req.User, req.PoolId, req.RewardDenom) if !found { - return nil, status.Error(codes.InvalidArgument, "invalid pool id") + return nil, status.Error(codes.InvalidArgument, "invalid pool id or denom") } return &types.QueryUserRewardInfoResponse{UserRewardInfo: userRewardInfo}, nil diff --git a/x/masterchef/types/expected_keepers.go b/x/masterchef/types/expected_keepers.go index 0ccee5a22..165a3cb3e 100644 --- a/x/masterchef/types/expected_keepers.go +++ b/x/masterchef/types/expected_keepers.go @@ -24,6 +24,7 @@ type CommitmentKeeper interface { MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnEdenBoost(ctx sdk.Context, creator string, denom string, amount math.Int) (ctypes.Commitments, error) SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + GetParams(sdk.Context) ctypes.Params } // Staking keeper diff --git a/x/stablestake/keeper/hooks.go b/x/stablestake/keeper/hooks.go new file mode 100644 index 000000000..c0789f5ca --- /dev/null +++ b/x/stablestake/keeper/hooks.go @@ -0,0 +1,38 @@ +package keeper + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/elys-network/elys/x/stablestake/types" +) + +var _ types.StableStakeHooks = MultiStableStakeHooks{} + +// combine multiple stablestake hooks, all hook functions are run in array sequence +type MultiStableStakeHooks []types.StableStakeHooks + +func NewMultiStableStakeHooks(hooks ...types.StableStakeHooks) MultiStableStakeHooks { + return hooks +} + +// Committed is called when staker committed his token +func (mh MultiStableStakeHooks) AfterBond(ctx sdk.Context, sender string, shareAmount math.Int) error { + for i := range mh { + err := mh[i].AfterBond(ctx, sender, shareAmount) + if err != nil { + return err + } + } + return nil +} + +// Committed is called when staker committed his token +func (mh MultiStableStakeHooks) AfterUnbond(ctx sdk.Context, sender string, shareAmount math.Int) error { + for i := range mh { + err := mh[i].AfterUnbond(ctx, sender, shareAmount) + if err != nil { + return err + } + } + return nil +} diff --git a/x/stablestake/keeper/keeper.go b/x/stablestake/keeper/keeper.go index a4a67f619..08e7bd643 100644 --- a/x/stablestake/keeper/keeper.go +++ b/x/stablestake/keeper/keeper.go @@ -21,6 +21,7 @@ type ( bk types.BankKeeper commitmentKeeper *commitmentkeeper.Keeper assetProfileKeeper types.AssetProfileKeeper + hooks types.StableStakeHooks } ) @@ -52,3 +53,14 @@ func NewKeeper( func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } + +// SetHooks set the epoch hooks +func (k *Keeper) SetHooks(eh types.StableStakeHooks) *Keeper { + if k.hooks != nil { + panic("cannot set stablestake hooks twice") + } + + k.hooks = eh + + return k +} diff --git a/x/stablestake/keeper/msg_server_bond.go b/x/stablestake/keeper/msg_server_bond.go index 435a4c140..af7ea5f6d 100644 --- a/x/stablestake/keeper/msg_server_bond.go +++ b/x/stablestake/keeper/msg_server_bond.go @@ -42,10 +42,10 @@ func (k msgServer) Bond(goCtx context.Context, msg *types.MsgBond) (*types.MsgBo return nil, err } - entry, found := k.assetProfileKeeper.GetEntry(ctx, shareDenom) + _, found := k.assetProfileKeeper.GetEntry(ctx, shareDenom) if !found { // Set an entity to assetprofile - entry = assetprofiletypes.Entry{ + entry := assetprofiletypes.Entry{ Authority: authtypes.NewModuleAddress(types.ModuleName).String(), BaseDenom: shareDenom, Decimals: ptypes.BASE_DECIMAL, @@ -88,5 +88,12 @@ func (k msgServer) Bond(goCtx context.Context, msg *types.MsgBond) (*types.MsgBo params.TotalValue = params.TotalValue.Add(msg.Amount) k.SetParams(ctx, params) + if k.hooks != nil { + err := k.hooks.AfterBond(ctx, msg.Creator, shareAmount) + if err != nil { + return nil, err + } + } + return &types.MsgBondResponse{}, nil } diff --git a/x/stablestake/keeper/msg_server_unbond.go b/x/stablestake/keeper/msg_server_unbond.go index 1a0d84c61..fd65fbb24 100644 --- a/x/stablestake/keeper/msg_server_unbond.go +++ b/x/stablestake/keeper/msg_server_unbond.go @@ -52,5 +52,12 @@ func (k msgServer) Unbond(goCtx context.Context, msg *types.MsgUnbond) (*types.M params.TotalValue = params.TotalValue.Sub(redemptionAmount) k.SetParams(ctx, params) + if k.hooks != nil { + err := k.hooks.AfterUnbond(ctx, msg.Creator, msg.Amount) + if err != nil { + return nil, err + } + } + return &types.MsgUnbondResponse{}, nil } diff --git a/x/stablestake/types/interfaces.go b/x/stablestake/types/interfaces.go new file mode 100644 index 000000000..2dc90ded9 --- /dev/null +++ b/x/stablestake/types/interfaces.go @@ -0,0 +1,12 @@ +package types + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// StableStakeHooks event hooks for stablestake processing +type StableStakeHooks interface { + AfterBond(ctx sdk.Context, sender string, shareAmount math.Int) error + AfterUnbond(ctx sdk.Context, sender string, shareAmount math.Int) error +}