From cc691881056b3fc573d90dbb3b62c907afffe641 Mon Sep 17 00:00:00 2001 From: Amit Yadav Date: Tue, 1 Oct 2024 21:19:57 +0530 Subject: [PATCH] [Stablestake]: Dynamic Redemption Rate Implementation (#832) * add redemption rate * add tests * fix * update * fix --- x/stablestake/keeper/begin_blocker.go | 2 + x/stablestake/keeper/msg_server_bond.go | 8 ++- x/stablestake/keeper/msg_server_unbond.go | 3 +- .../keeper/msg_server_unbond_test.go | 3 +- x/stablestake/keeper/params.go | 11 +++ x/stablestake/keeper/params_test.go | 68 +++++++++++++++++++ x/stablestake/types/expected_keepers.go | 1 + 7 files changed, 91 insertions(+), 5 deletions(-) diff --git a/x/stablestake/keeper/begin_blocker.go b/x/stablestake/keeper/begin_blocker.go index e33095c79..71ea94315 100644 --- a/x/stablestake/keeper/begin_blocker.go +++ b/x/stablestake/keeper/begin_blocker.go @@ -16,6 +16,8 @@ func (k Keeper) BeginBlocker(ctx sdk.Context) { if epochPosition == 0 { // if epoch has passed rate := k.InterestRateComputation(ctx) params.InterestRate = rate + + params.RedemptionRate = k.GetRedemptionRate(ctx) k.SetParams(ctx, params) } k.SetInterest(ctx, uint64(ctx.BlockHeight()), types.InterestBlock{InterestRate: params.InterestRate, BlockTime: ctx.BlockTime().Unix(), BlockHeight: uint64(ctx.BlockHeight())}) diff --git a/x/stablestake/keeper/msg_server_bond.go b/x/stablestake/keeper/msg_server_bond.go index 075f6aba8..ac98b28d8 100644 --- a/x/stablestake/keeper/msg_server_bond.go +++ b/x/stablestake/keeper/msg_server_bond.go @@ -15,6 +15,7 @@ func (k msgServer) Bond(goCtx context.Context, msg *types.MsgBond) (*types.MsgBo params := k.GetParams(ctx) creator := sdk.MustAccAddressFromBech32(msg.Creator) + redemptionRate := k.GetRedemptionRate(ctx) depositDenom := k.GetDepositDenom(ctx) depositCoin := sdk.NewCoin(depositDenom, msg.Amount) @@ -24,10 +25,11 @@ func (k msgServer) Bond(goCtx context.Context, msg *types.MsgBond) (*types.MsgBo } shareDenom := types.GetShareDenom() - if params.RedemptionRate.IsZero() { - return nil, types.ErrRedemptionRateIsZero + // Initial case + if redemptionRate.IsZero() { + redemptionRate = sdk.OneDec() } - shareAmount := sdk.NewDecFromInt(depositCoin.Amount).Quo(params.RedemptionRate).RoundInt() + shareAmount := sdk.NewDecFromInt(depositCoin.Amount).Quo(redemptionRate).RoundInt() shareCoins := sdk.NewCoins(sdk.NewCoin(shareDenom, shareAmount)) err = k.bk.MintCoins(ctx, types.ModuleName, shareCoins) diff --git a/x/stablestake/keeper/msg_server_unbond.go b/x/stablestake/keeper/msg_server_unbond.go index 58ddf1276..90d350f47 100644 --- a/x/stablestake/keeper/msg_server_unbond.go +++ b/x/stablestake/keeper/msg_server_unbond.go @@ -12,6 +12,7 @@ func (k msgServer) Unbond(goCtx context.Context, msg *types.MsgUnbond) (*types.M params := k.GetParams(ctx) creator := sdk.MustAccAddressFromBech32(msg.Creator) + redemptionRate := k.GetRedemptionRate(ctx) shareDenom := types.GetShareDenom() @@ -33,7 +34,7 @@ func (k msgServer) Unbond(goCtx context.Context, msg *types.MsgUnbond) (*types.M return nil, err } - redemptionAmount := sdk.NewDecFromInt(shareCoin.Amount).Mul(params.RedemptionRate).RoundInt() + redemptionAmount := sdk.NewDecFromInt(shareCoin.Amount).Mul(redemptionRate).RoundInt() depositDenom := k.GetDepositDenom(ctx) redemptionCoin := sdk.NewCoin(depositDenom, redemptionAmount) diff --git a/x/stablestake/keeper/msg_server_unbond_test.go b/x/stablestake/keeper/msg_server_unbond_test.go index 8e091ca2a..dc4061dcb 100644 --- a/x/stablestake/keeper/msg_server_unbond_test.go +++ b/x/stablestake/keeper/msg_server_unbond_test.go @@ -85,7 +85,8 @@ func (suite *KeeperTestSuite) TestUnbond() { suite.Require().NoError(err) params := suite.app.StablestakeKeeper.GetParams(suite.ctx) - params.TotalValue = sdk.NewInt(1000_000_000) + params.TotalValue = sdk.NewInt(1000_000) + params.RedemptionRate = sdk.NewDec(1) suite.app.StablestakeKeeper.SetParams(suite.ctx, params) msgServer := keeper.NewMsgServerImpl(suite.app.StablestakeKeeper) diff --git a/x/stablestake/keeper/params.go b/x/stablestake/keeper/params.go index b0d39213c..eb1f0a1c0 100644 --- a/x/stablestake/keeper/params.go +++ b/x/stablestake/keeper/params.go @@ -38,3 +38,14 @@ func (k Keeper) GetDepositDenom(ctx sdk.Context) string { } return entry.Denom } + +func (k Keeper) GetRedemptionRate(ctx sdk.Context) sdk.Dec { + params := k.GetParams(ctx) + totalShares := k.bk.GetSupply(ctx, types.GetShareDenom()) + + if totalShares.Amount.IsZero() { + return sdk.ZeroDec() + } + + return params.TotalValue.ToLegacyDec().Quo(totalShares.Amount.ToLegacyDec()) +} diff --git a/x/stablestake/keeper/params_test.go b/x/stablestake/keeper/params_test.go index 4c5d95e7a..b8c164a97 100644 --- a/x/stablestake/keeper/params_test.go +++ b/x/stablestake/keeper/params_test.go @@ -3,7 +3,13 @@ package keeper_test import ( "testing" + "cosmossdk.io/math" + "github.com/cometbft/cometbft/crypto/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" testkeeper "github.com/elys-network/elys/testutil/keeper" + ptypes "github.com/elys-network/elys/x/parameter/types" + "github.com/elys-network/elys/x/stablestake/keeper" "github.com/elys-network/elys/x/stablestake/types" "github.com/stretchr/testify/require" ) @@ -16,3 +22,65 @@ func TestGetParams(t *testing.T) { require.EqualValues(t, params, k.GetParams(ctx)) } + +func (suite *KeeperTestSuite) TestGetRedemptionRate() { + for _, tc := range []struct { + desc string + senderInitBalance sdk.Coins + bondAmount math.Int + expSenderBalance sdk.Coins + expSenderCommit sdk.Coin + expPass bool + }{ + { + desc: "successful bonding process, redemption should be set", + senderInitBalance: sdk.Coins{sdk.NewInt64Coin(ptypes.BaseCurrency, 1000000)}, + bondAmount: sdk.NewInt(10000), + expSenderBalance: sdk.Coins{sdk.NewInt64Coin(ptypes.BaseCurrency, 990000)}.Sort(), + expSenderCommit: sdk.NewInt64Coin(types.GetShareDenom(), 10000), + expPass: true, + }, + { + desc: "lack of balance, redemption should not be set", + senderInitBalance: sdk.Coins{sdk.NewInt64Coin(ptypes.BaseCurrency, 1000000)}, + bondAmount: sdk.NewInt(10000000000000), + expSenderBalance: sdk.Coins{sdk.NewInt64Coin(ptypes.BaseCurrency, 1000000)}, + expSenderCommit: sdk.Coin{}, + expPass: false, + }, + } { + suite.Run(tc.desc, func() { + suite.SetupTest() + + // bootstrap accounts + sender := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + + // bootstrap balances + err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, tc.senderInitBalance) + suite.Require().NoError(err) + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, sender, tc.senderInitBalance) + suite.Require().NoError(err) + + msgServer := keeper.NewMsgServerImpl(suite.app.StablestakeKeeper) + _, err = msgServer.Bond( + sdk.WrapSDKContext(suite.ctx), + &types.MsgBond{ + Creator: sender.String(), + Amount: tc.bondAmount, + }) + if !tc.expPass { + suite.Require().Error(err) + + // Check redemption rate + rate := suite.app.StablestakeKeeper.GetRedemptionRate(suite.ctx) + suite.Require().Equal(sdk.ZeroDec(), rate) + } else { + suite.Require().NoError(err) + + // Check redemption rate + rate := suite.app.StablestakeKeeper.GetRedemptionRate(suite.ctx) + suite.Require().Equal(sdk.NewDec(1), rate) + } + }) + } +} diff --git a/x/stablestake/types/expected_keepers.go b/x/stablestake/types/expected_keepers.go index 145637e0a..e7868dcc2 100644 --- a/x/stablestake/types/expected_keepers.go +++ b/x/stablestake/types/expected_keepers.go @@ -16,6 +16,7 @@ type AccountKeeper interface { // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + GetSupply(ctx sdk.Context, denom string) sdk.Coin MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error