Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change reward distribution to auction off rewards #1321

Open
wants to merge 1 commit into
base: buyback-and-burn
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x/stakeibc/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochInfo epochstypes.EpochInf
k.TransferAllRewardTokens(ctx)
}
if epochInfo.Identifier == epochstypes.MINT_EPOCH {
k.AllocateHostZoneReward(ctx)
k.AllocateRewardsFromHostZones(ctx)
}
}

Expand Down
82 changes: 14 additions & 68 deletions x/stakeibc/keeper/reward_allocation.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,32 @@
package keeper

import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

auctiontypes "github.com/Stride-Labs/stride/v24/x/auction/types"
"github.com/Stride-Labs/stride/v24/x/stakeibc/types"
)

// Liquid Stake Reward Collector Balance
func (k Keeper) LiquidStakeRewardCollectorBalance(ctx sdk.Context, msgSvr types.MsgServer) bool {
k.Logger(ctx).Info("Liquid Staking reward collector balance")
rewardCollectorAddress := k.AccountKeeper.GetModuleAccount(ctx, types.RewardCollectorName).GetAddress()
rewardedTokens := k.bankKeeper.GetAllBalances(ctx, rewardCollectorAddress)
if rewardedTokens.IsEqual(sdk.Coins{}) {
k.Logger(ctx).Info("No reward to allocate from RewardCollector")
return false
}

rewardsAccrued := false
for _, token := range rewardedTokens {
// get hostzone by reward token (in ibc denom format)
hz, err := k.GetHostZoneFromIBCDenom(ctx, token.Denom)
if err != nil {
k.Logger(ctx).Info("Token denom %s in module account is not from a supported host zone", token.Denom)
continue
}

// liquid stake all tokens
msg := types.NewMsgLiquidStake(rewardCollectorAddress.String(), token.Amount, hz.HostDenom)
if err := msg.ValidateBasic(); err != nil {
k.Logger(ctx).Error(fmt.Sprintf("Liquid stake from reward collector address failed validate basic: %s", err.Error()))
continue
}
_, err = msgSvr.LiquidStake(ctx, msg)
if err != nil {
k.Logger(ctx).Error(fmt.Sprintf("Failed to liquid stake %s for hostzone %s: %s", token.String(), hz.ChainId, err.Error()))
continue
}
k.Logger(ctx).Info(fmt.Sprintf("Liquid staked %s for hostzone %s's accrued rewards", token.String(), hz.ChainId))
rewardsAccrued = true
}
return rewardsAccrued
}
// AuctionOffRewardCollectorBalance transfers all balances from the reward collector module account
// to the auction module account. If the reward collector has no balance, it does nothing.
func (k Keeper) AuctionOffRewardCollectorBalance(ctx sdk.Context) {
k.Logger(ctx).Info("Auctioning reward collector balance")

// Sweep stTokens from Reward Collector to Fee Collector
func (k Keeper) SweepStTokensFromRewardCollToFeeColl(ctx sdk.Context) error {
// Send all stTokens to fee collector to distribute to delegator later
rewardCollectorAddress := k.AccountKeeper.GetModuleAccount(ctx, types.RewardCollectorName).GetAddress()

rewardCollCoins := k.bankKeeper.GetAllBalances(ctx, rewardCollectorAddress)
k.Logger(ctx).Info(fmt.Sprintf("Reward collector has %s", rewardCollCoins.String()))
stTokens := sdk.NewCoins()
for _, token := range rewardCollCoins {
// get hostzone by reward token (in stToken denom format)
isStToken := k.CheckIsStToken(ctx, token.Denom)
k.Logger(ctx).Info(fmt.Sprintf("%s is stToken: %t", token.String(), isStToken))
if isStToken {
stTokens = append(stTokens, token)
}
rewardCollectorBalances := k.bankKeeper.GetAllBalances(ctx, rewardCollectorAddress)
if rewardCollectorBalances.Empty() {
k.Logger(ctx).Info("No rewards to auction from RewardCollector")
return
}
k.Logger(ctx).Info(fmt.Sprintf("Sending %s stTokens from %s to %s", stTokens.String(), types.RewardCollectorName, authtypes.FeeCollectorName))

err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.RewardCollectorName, authtypes.FeeCollectorName, stTokens)
err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.RewardCollectorName, auctiontypes.ModuleName, rewardCollectorBalances)
if err != nil {
return errorsmod.Wrapf(err, "Can't send coins from module %s to module %s", types.RewardCollectorName, authtypes.FeeCollectorName)
k.Logger(ctx).Info("Cannot send rewards from RewardCollector to Auction module: %w", err)
}
return nil
}

// (1) liquid stake reward collector balance, then (2) sweet stTokens from reward collector to fee collector
func (k Keeper) AllocateHostZoneReward(ctx sdk.Context) {
// TODO: Move LS function to keeper method instead of message server
msgSvr := NewMsgServerImpl(k)
if rewardsFound := k.LiquidStakeRewardCollectorBalance(ctx, msgSvr); !rewardsFound {
k.Logger(ctx).Info("No accrued rewards in the reward collector account")
return
}
if err := k.SweepStTokensFromRewardCollToFeeColl(ctx); err != nil {
k.Logger(ctx).Error(fmt.Sprintf("Unable to allocate host zone reward, err: %s", err.Error()))
}
// AllocateRewardsFromHostZones auctions off the reward collector balance
func (k Keeper) AllocateRewardsFromHostZones(ctx sdk.Context) {
k.AuctionOffRewardCollectorBalance(ctx)
}
90 changes: 19 additions & 71 deletions x/stakeibc/keeper/reward_allocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
_ "github.com/stretchr/testify/suite"

auctiontypes "github.com/Stride-Labs/stride/v24/x/auction/types"

epochtypes "github.com/Stride-Labs/stride/v24/x/epochs/types"
recordtypes "github.com/Stride-Labs/stride/v24/x/records/types"
stakeibctypes "github.com/Stride-Labs/stride/v24/x/stakeibc/types"
Expand Down Expand Up @@ -80,89 +82,35 @@ func (s *KeeperTestSuite) TestLiquidStakeRewardCollectorBalance_Success() {
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(IbcAtom, rewardAmount))
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(IbcOsmo, rewardAmount))

// Liquid stake all hostzone token then get stTokens back
rewardsAccrued := s.App.StakeibcKeeper.LiquidStakeRewardCollectorBalance(s.Ctx, s.GetMsgServer())
s.Require().True(rewardsAccrued, "rewards should have been liquid staked")
// Send all rewrds to auction module
s.App.StakeibcKeeper.AuctionOffRewardCollectorBalance(s.Ctx)

// Reward Collector acct should have all ibc/XXX tokens converted to stTokens
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StAtom, rewardAmount)
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StOsmo, rewardAmount)
// Check Auction module balance
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcAtom, rewardAmount)
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcOsmo, rewardAmount)

// Check RewardCollector module balance
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcOsmo, sdkmath.ZeroInt())
}

func (s *KeeperTestSuite) TestLiquidStakeRewardCollectorBalance_NoRewardsAccrued() {
s.SetupTestRewardAllocation()

// With no IBC tokens in the rewards collector account, the liquid stake rewards function should return false
rewardsAccrued := s.App.StakeibcKeeper.LiquidStakeRewardCollectorBalance(s.Ctx, s.GetMsgServer())
s.Require().False(rewardsAccrued, "no rewards should have been liquid staked")

// There should also be no stTokens in the account
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StOsmo, sdkmath.ZeroInt())
}

func (s *KeeperTestSuite) TestLiquidStakeRewardCollectorBalance_BalanceDoesNotBelongToHost() {
s.SetupTestRewardAllocation()
amount := sdkmath.NewInt(1000)

// Fund the reward collector with ibc/atom and a denom that is not registerd to a host zone
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(IbcAtom, amount))
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin("fake_denom", amount))

// Liquid stake should only succeed with atom
rewardsAccrued := s.App.StakeibcKeeper.LiquidStakeRewardCollectorBalance(s.Ctx, s.GetMsgServer())
s.Require().True(rewardsAccrued, "rewards should have been liquid staked")

// The atom should have been liquid staked
// balances should be 0 before
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StAtom, amount)

// But the fake denom and uosmo should not have been touched
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, "fake_denom", amount)
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StOsmo, sdkmath.ZeroInt())
}

func (s *KeeperTestSuite) TestSweepRewardCollToFeeCollector_Success() {
s.SetupTestRewardAllocation()
rewardAmount := sdkmath.NewInt(1000)

// Add stTokens to reward collector
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(StAtom, rewardAmount))
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(StOsmo, rewardAmount))

// Sweep stTokens from Reward Collector to Fee Collector
err := s.App.StakeibcKeeper.SweepStTokensFromRewardCollToFeeColl(s.Ctx)
s.Require().NoError(err)

// Fee Collector acct should have stTokens after they're swept there from Reward Collector
// The reward collector should have nothing
s.checkModuleAccountBalance(authtypes.FeeCollectorName, StAtom, rewardAmount)
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StAtom, sdkmath.ZeroInt())

s.checkModuleAccountBalance(authtypes.FeeCollectorName, StOsmo, rewardAmount)
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, StOsmo, sdkmath.ZeroInt())
}

func (s *KeeperTestSuite) TestSweepRewardCollToFeeCollector_NonStTokens() {
s.SetupTestRewardAllocation()
amount := sdkmath.NewInt(1000)
nonStTokenDenom := "XXX"

// Fund reward collector account with stTokens
s.FundModuleAccount(stakeibctypes.RewardCollectorName, sdk.NewCoin(nonStTokenDenom, amount))

// Sweep stTokens from Reward Collector to Fee Collector
err := s.App.StakeibcKeeper.SweepStTokensFromRewardCollToFeeColl(s.Ctx)
s.Require().NoError(err)
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcOsmo, sdkmath.ZeroInt())

// Reward Collector acct should still contain nonStTokenDenom after stTokens after they're swept
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, nonStTokenDenom, amount)
// With no IBC tokens in the rewards collector account, the auction off rewards function should do nothing
s.App.StakeibcKeeper.AuctionOffRewardCollectorBalance(s.Ctx)

// Fee Collector acct should have nothing
s.checkModuleAccountBalance(authtypes.FeeCollectorName, nonStTokenDenom, sdkmath.ZeroInt())
// balances should be 0 after
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(stakeibctypes.RewardCollectorName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcAtom, sdkmath.ZeroInt())
s.checkModuleAccountBalance(auctiontypes.ModuleName, IbcOsmo, sdkmath.ZeroInt())
}

// Test the process of a delegator claiming staking reward stTokens (tests that Fee Account can distribute arbitrary denoms)
Expand Down