From f86c78234e6aa3a9c099165b73c9a0dcbc4aad4b Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 26 Feb 2024 19:30:48 -0500 Subject: [PATCH 01/20] add a message to withdraw emission rewards --- proto/emissions/tx.proto | 14 +- proto/emissions/withdrawable_emissions.proto | 9 + testutil/keeper/keeper.go | 2 +- testutil/keeper/mocks/emissions/bank.go | 18 + testutil/sample/emissions.go | 10 + x/emissions/abci.go | 29 + x/emissions/abci_test.go | 90 +++ .../client/cli/tx_create_withdraw_emssions.go | 37 ++ .../keeper/msg_server_withdraw_emissions.go | 18 + x/emissions/keeper/withdraw_emissions.go | 59 ++ x/emissions/keeper/withdraw_emmsions_test.go | 107 ++++ x/emissions/keeper/withdrawable_emissions.go | 13 + x/emissions/module.go | 3 +- x/emissions/types/errors.go | 14 +- x/emissions/types/expected_keepers.go | 1 + x/emissions/types/keys.go | 1 + .../types/message_withdraw_emissions.go | 50 ++ .../types/message_withdraw_emissons_test.go | 30 + x/emissions/types/tx.pb.go | 513 +++++++++++++++++- .../types/withdrawable_emissions.pb.go | 283 +++++++++- 20 files changed, 1273 insertions(+), 28 deletions(-) create mode 100644 x/emissions/client/cli/tx_create_withdraw_emssions.go create mode 100644 x/emissions/keeper/msg_server_withdraw_emissions.go create mode 100644 x/emissions/keeper/withdraw_emissions.go create mode 100644 x/emissions/keeper/withdraw_emmsions_test.go create mode 100644 x/emissions/types/message_withdraw_emissions.go create mode 100644 x/emissions/types/message_withdraw_emissons_test.go diff --git a/proto/emissions/tx.proto b/proto/emissions/tx.proto index 0d8c4422ec..7ea3bba01d 100644 --- a/proto/emissions/tx.proto +++ b/proto/emissions/tx.proto @@ -6,4 +6,16 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/zeta-chain/zetacore/x/emissions/types"; // Msg defines the Msg service. -service Msg {} +service Msg { + rpc WithdrawEmission(MsgWithdrawEmission) returns (MsgWithdrawEmissionResponse); +} + +message MsgWithdrawEmission { + string creator = 1; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message MsgWithdrawEmissionResponse {} diff --git a/proto/emissions/withdrawable_emissions.proto b/proto/emissions/withdrawable_emissions.proto index 9af2d5569b..5b97b903a3 100644 --- a/proto/emissions/withdrawable_emissions.proto +++ b/proto/emissions/withdrawable_emissions.proto @@ -12,3 +12,12 @@ message WithdrawableEmissions { (gogoproto.nullable) = false ]; } + +message WithdrawEmission { + string address = 1; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string withdraw_failed_reason = 4; +} diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index 82d02c9c4b..a5e9e8851c 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -106,7 +106,7 @@ var moduleAccountPerms = map[string][]string{ crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, emissionstypes.ModuleName: {authtypes.Minter}, - emissionstypes.UndistributedObserverRewardsPool: nil, + emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter}, emissionstypes.UndistributedTssRewardsPool: nil, } diff --git a/testutil/keeper/mocks/emissions/bank.go b/testutil/keeper/mocks/emissions/bank.go index 2e3d6a702e..8149b5e6af 100644 --- a/testutil/keeper/mocks/emissions/bank.go +++ b/testutil/keeper/mocks/emissions/bank.go @@ -31,6 +31,24 @@ func (_m *EmissionBankKeeper) GetBalance(ctx types.Context, addr types.AccAddres return r0 } +// SendCoinsFromModuleToAccount provides a mock function with given fields: ctx, senderModule, recipientAddr, amt +func (_m *EmissionBankKeeper) SendCoinsFromModuleToAccount(ctx types.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error { + ret := _m.Called(ctx, senderModule, recipientAddr, amt) + + if len(ret) == 0 { + panic("no return value specified for SendCoinsFromModuleToAccount") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, string, types.AccAddress, types.Coins) error); ok { + r0 = rf(ctx, senderModule, recipientAddr, amt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SendCoinsFromModuleToModule provides a mock function with given fields: ctx, senderModule, recipientModule, amt func (_m *EmissionBankKeeper) SendCoinsFromModuleToModule(ctx types.Context, senderModule string, recipientModule string, amt types.Coins) error { ret := _m.Called(ctx, senderModule, recipientModule, amt) diff --git a/testutil/sample/emissions.go b/testutil/sample/emissions.go index 491f16e501..9e5c39e8af 100644 --- a/testutil/sample/emissions.go +++ b/testutil/sample/emissions.go @@ -16,3 +16,13 @@ func WithdrawableEmissions(t *testing.T) types.WithdrawableEmissions { Amount: math.NewInt(r.Int63()), } } + +func WithdrawEmission(t *testing.T) types.WithdrawEmission { + addr := AccAddress() + r := newRandFromStringSeed(t, addr) + + return types.WithdrawEmission{ + Address: AccAddress(), + Amount: math.NewInt(r.Int63()), + } +} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 2fb0561bb6..7a3285ffb2 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -146,3 +146,32 @@ func DistributeTssRewards(ctx sdk.Context, amount sdk.Int, bankKeeper types.Bank coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, amount)) return bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.UndistributedTssRewardsPool, coin) } + +func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) { + allWithdrawEmissions := keeper.GetAllWithdrawEmissions(ctx) + defer func() { + for _, withdrawEmission := range allWithdrawEmissions { + keeper.DeleteWithdrawEmissions(ctx, withdrawEmission.Address) + } + }() + for _, withdrawEmission := range allWithdrawEmissions { + tmpCtx, commit := ctx.CacheContext() + coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawEmission.Amount)) + address, err := sdk.AccAddressFromBech32(withdrawEmission.Address) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error while parsing withdraw emission address %s : %s", withdrawEmission.Address, err)) + continue + } + err = keeper.RemoveObserverEmission(tmpCtx, withdrawEmission.Address, withdrawEmission.Amount) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission from observer %s : %s", withdrawEmission.Address, err)) + continue + } + err = keeper.GetBankKeeper().SendCoinsFromModuleToAccount(tmpCtx, types.UndistributedObserverRewardsPool, address, coin) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error while sending withdraw emission to %s : %s", withdrawEmission.Address, err)) + continue + } + commit() + } +} diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index 55e6188ba9..669fe5b53a 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -16,6 +16,96 @@ import ( observerTypes "github.com/zeta-chain/zetacore/x/observer/types" ) +func TestEndBlocker(t *testing.T) { + t.Run("successfully process all emissons created in a block", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + totalAmount := sdkmath.ZeroInt() + emissions := make([]emissionstypes.WithdrawEmission, 10) + + for i := 0; i < 10; i++ { + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: emission.Address, + Amount: emission.Amount, + }) + emissions[i] = emission + totalAmount = totalAmount.Add(emission.Amount) + } + + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalAmount))) + require.NoError(t, err) + + require.ElementsMatch(t, emissions, k.GetAllWithdrawEmissions(ctx)) + emissionsModule.EndBlocker(ctx, *k) + require.Empty(t, k.GetAllWithdrawEmissions(ctx)) + for _, emission := range emissions { + address, err := sdk.AccAddressFromBech32(emission.Address) + require.NoError(t, err) + balance := k.GetBankKeeper().GetBalance(ctx, address, config.BaseDenom) + require.Equal(t, emission.Amount.String(), balance.Amount.String()) + } + }) + + t.Run("do not commit unsuccessful withdraws to state", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() + + totalAmount := sdkmath.ZeroInt() + emissions := make([]emissionstypes.WithdrawEmission, 10) + unsuccessfulIndex := 5 + for i := 0; i < 10; i++ { + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + emissions[i] = emission + totalAmount = totalAmount.Add(emission.Amount) + if i == unsuccessfulIndex { + continue + } + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: emission.Address, + Amount: emission.Amount, + }) + } + + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalAmount))) + require.NoError(t, err) + + require.ElementsMatch(t, emissions, k.GetAllWithdrawEmissions(ctx)) + emissionsModule.EndBlocker(ctx, *k) + require.Empty(t, k.GetAllWithdrawEmissions(ctx)) + for i, emission := range emissions { + address, err := sdk.AccAddressFromBech32(emission.Address) + require.NoError(t, err) + balance := k.GetBankKeeper().GetBalance(ctx, address, config.BaseDenom) + if i == unsuccessfulIndex { + require.Equal(t, sdkmath.ZeroInt().String(), balance.Amount.String()) + continue + } + require.Equal(t, emission.Amount.String(), balance.Amount.String()) + } + observerPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount + require.Equal(t, emissions[unsuccessfulIndex].Amount.String(), observerPoolBalances.String()) + }) + t.Run("unable to process withdraw if address is invalid", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() + + emission := sample.WithdrawEmission(t) + emission.Address = "invalid_address" + k.SetWithdrawEmissions(ctx, emission) + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: emission.Address, + Amount: emission.Amount, + }) + + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emission.Amount))) + require.NoError(t, err) + emissionsModule.EndBlocker(ctx, *k) + require.Empty(t, k.GetAllWithdrawEmissions(ctx)) + require.Equal(t, emission.Amount.String(), sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount.String()) + }) +} func TestBeginBlocker(t *testing.T) { t.Run("no observer distribution happens if emissions module account is empty", func(t *testing.T) { k, ctx, _, zk := keepertest.EmissionsKeeper(t) diff --git a/x/emissions/client/cli/tx_create_withdraw_emssions.go b/x/emissions/client/cli/tx_create_withdraw_emssions.go new file mode 100644 index 0000000000..c86f29ade3 --- /dev/null +++ b/x/emissions/client/cli/tx_create_withdraw_emssions.go @@ -0,0 +1,37 @@ +package cli + +import ( + "errors" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func CmdCreateWithdrawEmission() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-withdraw-emission", + Short: "create a new withdrawEmission", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + argsAmount, ok := sdkmath.NewIntFromString(args[0]) + if !ok { + return errors.New("invalid amount") + } + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + msg := types.NewMsgWithdrawEmissions(clientCtx.GetFromAddress().String(), argsAmount) + if err := msg.ValidateBasic(); err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go new file mode 100644 index 0000000000..31b4a90f9c --- /dev/null +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -0,0 +1,18 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdrawEmission) (*types.MsgWithdrawEmissionResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + err := k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount) + if err != nil { + return nil, errorsmod.Wrap(types.ErrUnableToCreateWithdrawEmissions, err.Error()) + } + return &types.MsgWithdrawEmissionResponse{}, nil +} diff --git a/x/emissions/keeper/withdraw_emissions.go b/x/emissions/keeper/withdraw_emissions.go new file mode 100644 index 0000000000..d481f2aed0 --- /dev/null +++ b/x/emissions/keeper/withdraw_emissions.go @@ -0,0 +1,59 @@ +package keeper + +import ( + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func (k Keeper) SetWithdrawEmissions(ctx sdk.Context, we types.WithdrawEmission) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) + b := k.cdc.MustMarshal(&we) + store.Set([]byte(we.Address), b) +} + +func (k Keeper) GetWithdrawEmissions(ctx sdk.Context, address string) (val types.WithdrawEmission, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) + b := store.Get(types.KeyPrefix(address)) + if b == nil { + return val, false + } + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +func (k Keeper) DeleteWithdrawEmissions(ctx sdk.Context, address string) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) + store.Delete([]byte(address)) +} + +func (k Keeper) GetAllWithdrawEmissions(ctx sdk.Context) (list []types.WithdrawEmission) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var val types.WithdrawEmission + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + return +} + +func (k Keeper) CreateWithdrawEmissions(ctx sdk.Context, address string, amount sdkmath.Int) error { + emissions, found := k.GetWithdrawableEmission(ctx, address) + if !found { + return types.ErrEmissionsNotFound + } + if amount.IsNegative() || amount.IsZero() { + return types.ErrNotEnoughEmissionsAvailable + } + if amount.GT(emissions.Amount) { + amount = emissions.Amount + } + k.SetWithdrawEmissions(ctx, types.WithdrawEmission{ + Address: address, + Amount: amount, + }) + return nil +} diff --git a/x/emissions/keeper/withdraw_emmsions_test.go b/x/emissions/keeper/withdraw_emmsions_test.go new file mode 100644 index 0000000000..5d0c05754f --- /dev/null +++ b/x/emissions/keeper/withdraw_emmsions_test.go @@ -0,0 +1,107 @@ +package keeper_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestKeeper_WithdrawEmissions(t *testing.T) { + t.Run("set withdraw emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + em, found := k.GetWithdrawEmissions(ctx, emission.Address) + require.True(t, found) + require.Equal(t, emission, em) + }) + + t.Run("replace withdraw emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + oldAmount := emission.Amount + emission.Amount = sample.IntInRange(1, 100) + k.SetWithdrawEmissions(ctx, emission) + em, found := k.GetWithdrawEmissions(ctx, emission.Address) + require.True(t, found) + require.Equal(t, emission, em) + require.NotEqual(t, oldAmount, em.Amount) + }) + t.Run("unable to get withdraw emission which doesnt exist", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + _, found := k.GetWithdrawEmissions(ctx, sample.AccAddress()) + require.False(t, found) + }) + t.Run("delete withdraw emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + k.DeleteWithdrawEmissions(ctx, emission.Address) + _, found := k.GetWithdrawEmissions(ctx, emission.Address) + require.False(t, found) + }) + t.Run("get all withdraw emissions", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + emissions := make([]emissionstypes.WithdrawEmission, 10) + for i := 0; i < 10; i++ { + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + emissions[i] = emission + } + allEmissions := k.GetAllWithdrawEmissions(ctx) + require.ElementsMatch(t, emissions, allEmissions) + }) +} + +func TestKeeper_CreateWithdrawEmissions(t *testing.T) { + t.Run("create withdraw emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, withdrawableEmission.Amount) + require.NoError(t, err) + em, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) + require.True(t, found) + require.Equal(t, withdrawableEmission.Amount, em.Amount) + }) + + t.Run("create withdraw for max available withdrawable emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, withdrawableEmission.Amount.Add(sample.IntInRange(1, 100))) + require.NoError(t, err) + em, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) + require.True(t, found) + require.Equal(t, withdrawableEmission.Amount, em.Amount) + }) + + t.Run("unable to create withdraw for zero amount", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + withdrawableEmission := sample.WithdrawableEmissions(t) + withdrawableEmission.Amount = sdkmath.ZeroInt() + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.ZeroInt()) + require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + }) + + t.Run("unable to create withdraw for negative amount", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + withdrawableEmission := sample.WithdrawableEmissions(t) + withdrawableEmission.Amount = sdkmath.NewInt(-1) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.NewInt(-1)) + require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + }) + + t.Run("unable to create withdraw for non existing withdrawable emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + err := k.CreateWithdrawEmissions(ctx, sample.AccAddress(), sdkmath.NewInt(1)) + require.ErrorIs(t, err, emissionstypes.ErrEmissionsNotFound) + }) +} diff --git a/x/emissions/keeper/withdrawable_emissions.go b/x/emissions/keeper/withdrawable_emissions.go index cdb478eda8..36910be688 100644 --- a/x/emissions/keeper/withdrawable_emissions.go +++ b/x/emissions/keeper/withdrawable_emissions.go @@ -46,6 +46,19 @@ func (k Keeper) AddObserverEmission(ctx sdk.Context, address string, amount sdkm k.SetWithdrawableEmission(ctx, we) } +func (k Keeper) RemoveObserverEmission(ctx sdk.Context, address string, amount sdkmath.Int) error { + we, found := k.GetWithdrawableEmission(ctx, address) + if !found { + we = types.WithdrawableEmissions{Address: address, Amount: sdkmath.ZeroInt()} + } + if we.Amount.LT(amount) { + return types.ErrNotEnoughEmissionsAvailable + } + we.Amount = we.Amount.Sub(amount) + k.SetWithdrawableEmission(ctx, we) + return nil +} + // SlashObserverEmission slashes the rewards of a given address, if the address has no rewards left, it will set the rewards to 0. /* This function is a basic implementation of slashing; it will be improved in the future . Improvements will include: diff --git a/x/emissions/module.go b/x/emissions/module.go index 7086c998fc..487b951edb 100644 --- a/x/emissions/module.go +++ b/x/emissions/module.go @@ -174,6 +174,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { // EndBlock executes all ABCI EndBlock logic respective to the emissions module. It // returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + EndBlocker(ctx, am.keeper) return []abci.ValidatorUpdate{} } diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index 856baedd89..7e5bbf490c 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -1,14 +1,12 @@ package types -// DONTCOVER +import errorsmod "cosmossdk.io/errors" -import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) +// DONTCOVER -// x/emissions module sentinel errors var ( - ErrEmissionTrackerNotFound = sdkerrors.Register(ModuleName, 1100, "Emission Tracker Not found") - ErrParsingSenderAddress = sdkerrors.Register(ModuleName, 1101, "Unable to parse address of sender") - ErrAddingCoinstoTracker = sdkerrors.Register(ModuleName, 1102, "Unable to add coins to emissionTracker ") + ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") + + ErrNotEnoughEmissionsAvailable = errorsmod.Register(ModuleName, 1001, "Not enough emissions available to withdraw") + ErrUnableToCreateWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to create withdraw emissions") ) diff --git a/x/emissions/types/expected_keepers.go b/x/emissions/types/expected_keepers.go index 9facd542b8..286490e7c5 100644 --- a/x/emissions/types/expected_keepers.go +++ b/x/emissions/types/expected_keepers.go @@ -20,6 +20,7 @@ type ObserverKeeper interface { // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin // Methods imported from bank should be defined here } diff --git a/x/emissions/types/keys.go b/x/emissions/types/keys.go index 9f1fa705fc..d016997343 100644 --- a/x/emissions/types/keys.go +++ b/x/emissions/types/keys.go @@ -23,6 +23,7 @@ const ( // MemStoreKey defines the in-memory store key MemStoreKey = "mem_emissions" WithdrawableEmissionsKey = "WithdrawableEmissions-value-" + WithdrawEmissionsKey = "WithdrawEmissions-value-" SecsInMonth = 30 * 24 * 60 * 60 BlockRewardsInZeta = "210000000" diff --git a/x/emissions/types/message_withdraw_emissions.go b/x/emissions/types/message_withdraw_emissions.go new file mode 100644 index 0000000000..34d02e8fa1 --- /dev/null +++ b/x/emissions/types/message_withdraw_emissions.go @@ -0,0 +1,50 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const MsgWithdrawEmissionType = "withdraw_emission" + +var _ sdk.Msg = &MsgWithdrawEmission{} + +// NewMsgWithdrawEmission creates a new MsgWithdrawEmission instance + +func NewMsgWithdrawEmissions(creator string, amount sdkmath.Int) *MsgWithdrawEmission { + return &MsgWithdrawEmission{Creator: creator, Amount: amount} +} + +func (msg *MsgWithdrawEmission) Route() string { + return RouterKey +} + +func (msg *MsgWithdrawEmission) Type() string { + return MsgWithdrawEmissionType +} + +func (msg *MsgWithdrawEmission) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgWithdrawEmission) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgWithdrawEmission) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + if !msg.Amount.GTE(sdkmath.ZeroInt()) { + return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "invalid amount (%s)", msg.Amount.String()) + } + return nil +} diff --git a/x/emissions/types/message_withdraw_emissons_test.go b/x/emissions/types/message_withdraw_emissons_test.go new file mode 100644 index 0000000000..26872beefb --- /dev/null +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -0,0 +1,30 @@ +package types_test + +import ( + "testing" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/testutil/sample" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestMsgWithdrawEmission_ValidateBasic(t *testing.T) { + t.Run("invalid creator address", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions("invalid_address", sample.IntInRange(1, 100)) + err := msg.ValidateBasic() + require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) + }) + + t.Run("valid withdraw message", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) + err := msg.ValidateBasic() + require.NoError(t, err) + }) + + t.Run("invalid amount", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(-100, -1)) + err := msg.ValidateBasic() + require.ErrorIs(t, err, sdkerrors.ErrInvalidCoins) + }) +} diff --git a/x/emissions/types/tx.pb.go b/x/emissions/types/tx.pb.go index 1305c4182f..7b59eb8cc2 100644 --- a/x/emissions/types/tx.pb.go +++ b/x/emissions/types/tx.pb.go @@ -6,12 +6,17 @@ package types import ( context "context" fmt "fmt" + io "io" math "math" + math_bits "math/bits" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" ) // Reference imports to suppress errors if they are not otherwise used. @@ -25,20 +30,113 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type MsgWithdrawEmission struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` +} + +func (m *MsgWithdrawEmission) Reset() { *m = MsgWithdrawEmission{} } +func (m *MsgWithdrawEmission) String() string { return proto.CompactTextString(m) } +func (*MsgWithdrawEmission) ProtoMessage() {} +func (*MsgWithdrawEmission) Descriptor() ([]byte, []int) { + return fileDescriptor_618f91fd090d1520, []int{0} +} +func (m *MsgWithdrawEmission) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWithdrawEmission) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWithdrawEmission.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWithdrawEmission) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWithdrawEmission.Merge(m, src) +} +func (m *MsgWithdrawEmission) XXX_Size() int { + return m.Size() +} +func (m *MsgWithdrawEmission) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWithdrawEmission.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWithdrawEmission proto.InternalMessageInfo + +func (m *MsgWithdrawEmission) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +type MsgWithdrawEmissionResponse struct { +} + +func (m *MsgWithdrawEmissionResponse) Reset() { *m = MsgWithdrawEmissionResponse{} } +func (m *MsgWithdrawEmissionResponse) String() string { return proto.CompactTextString(m) } +func (*MsgWithdrawEmissionResponse) ProtoMessage() {} +func (*MsgWithdrawEmissionResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_618f91fd090d1520, []int{1} +} +func (m *MsgWithdrawEmissionResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWithdrawEmissionResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWithdrawEmissionResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWithdrawEmissionResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWithdrawEmissionResponse.Merge(m, src) +} +func (m *MsgWithdrawEmissionResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgWithdrawEmissionResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWithdrawEmissionResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWithdrawEmissionResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgWithdrawEmission)(nil), "zetachain.zetacore.emissions.MsgWithdrawEmission") + proto.RegisterType((*MsgWithdrawEmissionResponse)(nil), "zetachain.zetacore.emissions.MsgWithdrawEmissionResponse") +} + func init() { proto.RegisterFile("emissions/tx.proto", fileDescriptor_618f91fd090d1520) } var fileDescriptor_618f91fd090d1520 = []byte{ - // 147 bytes of a gzipped FileDescriptorProto + // 272 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4a, 0xcd, 0xcd, 0x2c, 0x2e, 0xce, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0xa9, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x03, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, - 0xe0, 0xca, 0xa4, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x0a, 0xf5, 0x41, 0x2c, 0x88, 0x1e, 0x23, - 0x56, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0x27, 0xaf, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, - 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, - 0x63, 0x88, 0x32, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x07, 0x99, - 0xaa, 0x0b, 0xb6, 0x40, 0x1f, 0x66, 0x81, 0x7e, 0x85, 0x3e, 0x92, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, - 0x93, 0xd8, 0xc0, 0x26, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x92, 0xdd, 0x96, 0x7e, 0xa3, - 0x00, 0x00, 0x00, + 0xe0, 0xca, 0xa4, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x0a, 0xf5, 0x41, 0x2c, 0x88, 0x1e, 0xa5, + 0x72, 0x2e, 0x61, 0xdf, 0xe2, 0xf4, 0xf0, 0xcc, 0x92, 0x8c, 0x94, 0xa2, 0xc4, 0x72, 0x57, 0xa8, + 0x6a, 0x21, 0x09, 0x2e, 0xf6, 0xe4, 0xa2, 0xd4, 0xc4, 0x92, 0xfc, 0x22, 0x09, 0x46, 0x05, 0x46, + 0x0d, 0xce, 0x20, 0x18, 0x57, 0xc8, 0x8d, 0x8b, 0x2d, 0x31, 0x37, 0xbf, 0x34, 0xaf, 0x44, 0x82, + 0x09, 0x24, 0xe1, 0xa4, 0x77, 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0x6a, 0xe9, 0x99, 0x25, + 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0x50, 0x4a, + 0xb7, 0x38, 0x25, 0x5b, 0xbf, 0xa4, 0xb2, 0x20, 0xb5, 0x58, 0xcf, 0x33, 0xaf, 0x24, 0x08, 0xaa, + 0x5b, 0x49, 0x96, 0x4b, 0x1a, 0x8b, 0xc5, 0x41, 0xa9, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x46, + 0x1d, 0x8c, 0x5c, 0xcc, 0xbe, 0xc5, 0xe9, 0x42, 0x0d, 0x8c, 0x5c, 0x02, 0x18, 0xae, 0x33, 0xd4, + 0xc3, 0xe7, 0x53, 0x3d, 0x2c, 0xe6, 0x4a, 0x59, 0x92, 0xac, 0x05, 0xe6, 0x14, 0x27, 0xaf, 0x13, + 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, + 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x40, 0xf2, 0x33, 0xc8, 0x50, 0x5d, + 0xb0, 0xf9, 0xfa, 0x30, 0xf3, 0xf5, 0x2b, 0xf4, 0x91, 0x62, 0x09, 0x14, 0x02, 0x49, 0x6c, 0xe0, + 0x50, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x84, 0x03, 0xec, 0xd0, 0xbf, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -53,6 +151,7 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + WithdrawEmission(ctx context.Context, in *MsgWithdrawEmission, opts ...grpc.CallOption) (*MsgWithdrawEmissionResponse, error) } type msgClient struct { @@ -63,22 +162,414 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) WithdrawEmission(ctx context.Context, in *MsgWithdrawEmission, opts ...grpc.CallOption) (*MsgWithdrawEmissionResponse, error) { + out := new(MsgWithdrawEmissionResponse) + err := c.cc.Invoke(ctx, "/zetachain.zetacore.emissions.Msg/WithdrawEmission", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { + WithdrawEmission(context.Context, *MsgWithdrawEmission) (*MsgWithdrawEmissionResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) WithdrawEmission(ctx context.Context, req *MsgWithdrawEmission) (*MsgWithdrawEmissionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawEmission not implemented") +} + func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_WithdrawEmission_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgWithdrawEmission) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).WithdrawEmission(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/zetachain.zetacore.emissions.Msg/WithdrawEmission", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).WithdrawEmission(ctx, req.(*MsgWithdrawEmission)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "zetachain.zetacore.emissions.Msg", HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "emissions/tx.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "WithdrawEmission", + Handler: _Msg_WithdrawEmission_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "emissions/tx.proto", +} + +func (m *MsgWithdrawEmission) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWithdrawEmission) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWithdrawEmission) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgWithdrawEmissionResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWithdrawEmissionResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWithdrawEmissionResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgWithdrawEmission) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgWithdrawEmissionResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *MsgWithdrawEmission) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWithdrawEmission: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWithdrawEmission: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgWithdrawEmissionResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWithdrawEmissionResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWithdrawEmissionResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/emissions/types/withdrawable_emissions.pb.go b/x/emissions/types/withdrawable_emissions.pb.go index b1e0f07e4f..b8c49d770c 100644 --- a/x/emissions/types/withdrawable_emissions.pb.go +++ b/x/emissions/types/withdrawable_emissions.pb.go @@ -70,8 +70,62 @@ func (m *WithdrawableEmissions) GetAddress() string { return "" } +type WithdrawEmission struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` + WithdrawFailedReason string `protobuf:"bytes,4,opt,name=withdraw_failed_reason,json=withdrawFailedReason,proto3" json:"withdraw_failed_reason,omitempty"` +} + +func (m *WithdrawEmission) Reset() { *m = WithdrawEmission{} } +func (m *WithdrawEmission) String() string { return proto.CompactTextString(m) } +func (*WithdrawEmission) ProtoMessage() {} +func (*WithdrawEmission) Descriptor() ([]byte, []int) { + return fileDescriptor_56e0acf72be654f9, []int{1} +} +func (m *WithdrawEmission) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *WithdrawEmission) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_WithdrawEmission.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *WithdrawEmission) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawEmission.Merge(m, src) +} +func (m *WithdrawEmission) XXX_Size() int { + return m.Size() +} +func (m *WithdrawEmission) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawEmission.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawEmission proto.InternalMessageInfo + +func (m *WithdrawEmission) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *WithdrawEmission) GetWithdrawFailedReason() string { + if m != nil { + return m.WithdrawFailedReason + } + return "" +} + func init() { proto.RegisterType((*WithdrawableEmissions)(nil), "zetachain.zetacore.emissions.WithdrawableEmissions") + proto.RegisterType((*WithdrawEmission)(nil), "zetachain.zetacore.emissions.WithdrawEmission") } func init() { @@ -79,7 +133,7 @@ func init() { } var fileDescriptor_56e0acf72be654f9 = []byte{ - // 233 bytes of a gzipped FileDescriptorProto + // 278 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4b, 0xcd, 0xcd, 0x2c, 0x2e, 0xce, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x4f, 0x4c, 0xca, 0x49, 0x8d, 0x87, 0x0b, 0xeb, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xc9, 0x54, 0xa5, 0x96, @@ -90,11 +144,14 @@ var fileDescriptor_56e0acf72be654f9 = []byte{ 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x13, 0x48, 0xc2, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, 0xe4, 0xd5, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xa1, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, - 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0x93, 0xd7, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, - 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, - 0x31, 0x44, 0x19, 0x20, 0x99, 0x04, 0xf2, 0x89, 0x2e, 0xd8, 0x53, 0xfa, 0x30, 0x4f, 0xe9, 0x57, - 0xe8, 0x23, 0x42, 0x04, 0x6c, 0x6e, 0x12, 0x1b, 0xd8, 0x37, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x99, 0xf1, 0xaf, 0x2c, 0x2b, 0x01, 0x00, 0x00, + 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0xd2, 0x2a, 0x46, 0x2e, 0x01, 0x98, 0xdd, 0x30, 0x7b, 0x69, + 0x6f, 0xad, 0x90, 0x09, 0x97, 0x18, 0x2c, 0x14, 0xe3, 0xd3, 0x12, 0x33, 0x73, 0x52, 0x53, 0xe2, + 0x8b, 0x52, 0x13, 0x8b, 0xf3, 0xf3, 0x24, 0x58, 0xc0, 0x16, 0x8a, 0xc0, 0x64, 0xdd, 0xc0, 0x92, + 0x41, 0x60, 0x39, 0x27, 0xaf, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, + 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, + 0x40, 0xb2, 0x1f, 0x14, 0xec, 0xba, 0xe0, 0x18, 0xd0, 0x87, 0xc5, 0x80, 0x7e, 0x85, 0x3e, 0x22, + 0xfa, 0xc0, 0xae, 0x49, 0x62, 0x03, 0x07, 0xbd, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x53, 0x9f, + 0x6e, 0xbe, 0xd8, 0x01, 0x00, 0x00, } func (m *WithdrawableEmissions) Marshal() (dAtA []byte, err error) { @@ -137,6 +194,53 @@ func (m *WithdrawableEmissions) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *WithdrawEmission) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WithdrawEmission) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WithdrawEmission) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.WithdrawFailedReason) > 0 { + i -= len(m.WithdrawFailedReason) + copy(dAtA[i:], m.WithdrawFailedReason) + i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(len(m.WithdrawFailedReason))) + i-- + dAtA[i] = 0x22 + } + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintWithdrawableEmissions(dAtA []byte, offset int, v uint64) int { offset -= sovWithdrawableEmissions(v) base := offset @@ -163,6 +267,25 @@ func (m *WithdrawableEmissions) Size() (n int) { return n } +func (m *WithdrawEmission) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovWithdrawableEmissions(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovWithdrawableEmissions(uint64(l)) + l = len(m.WithdrawFailedReason) + if l > 0 { + n += 1 + l + sovWithdrawableEmissions(uint64(l)) + } + return n +} + func sovWithdrawableEmissions(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -285,6 +408,154 @@ func (m *WithdrawableEmissions) Unmarshal(dAtA []byte) error { } return nil } +func (m *WithdrawEmission) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWithdrawableEmissions + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WithdrawEmission: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WithdrawEmission: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWithdrawableEmissions + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWithdrawableEmissions + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WithdrawFailedReason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWithdrawableEmissions + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WithdrawFailedReason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWithdrawableEmissions(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWithdrawableEmissions + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipWithdrawableEmissions(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 7599d9b894161f211f1df09a15124b07f86e2dc3 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 27 Feb 2024 00:36:09 -0500 Subject: [PATCH 02/20] add more tests for end blocker --- x/emissions/abci_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index 669fe5b53a..247a682879 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -87,6 +87,7 @@ func TestEndBlocker(t *testing.T) { observerPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount require.Equal(t, emissions[unsuccessfulIndex].Amount.String(), observerPoolBalances.String()) }) + t.Run("unable to process withdraw if address is invalid", func(t *testing.T) { k, ctx, sk, _ := keepertest.EmissionsKeeper(t) undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() @@ -105,6 +106,39 @@ func TestEndBlocker(t *testing.T) { require.Empty(t, k.GetAllWithdrawEmissions(ctx)) require.Equal(t, emission.Amount.String(), sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount.String()) }) + + t.Run("unable to process withdraw if amount is more than available emission", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() + + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: emission.Address, + Amount: emission.Amount.Sub(sdkmath.NewInt(1)), + }) + + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emission.Amount))) + require.NoError(t, err) + emissionsModule.EndBlocker(ctx, *k) + require.Empty(t, k.GetAllWithdrawEmissions(ctx)) + require.Equal(t, emission.Amount.String(), sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount.String()) + }) + + t.Run("unable to process withdraw if UndistributedObserverRewardsPool does not have enough balance", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + + emission := sample.WithdrawEmission(t) + k.SetWithdrawEmissions(ctx, emission) + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: emission.Address, + Amount: emission.Amount, + }) + + emissionsModule.EndBlocker(ctx, *k) + require.Empty(t, k.GetAllWithdrawEmissions(ctx)) + require.Equal(t, sdkmath.ZeroInt().String(), sk.BankKeeper.GetBalance(ctx, sdk.MustAccAddressFromBech32(emission.Address), config.BaseDenom).Amount.String()) + }) } func TestBeginBlocker(t *testing.T) { t.Run("no observer distribution happens if emissions module account is empty", func(t *testing.T) { From 7d4c3cb20033b325cbe1e84b700f1d6fda1e032e Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 27 Feb 2024 02:23:25 -0500 Subject: [PATCH 03/20] add unit tests for withdrawable emissions --- changelog.md | 1 + .../keeper/withdrable_emissions_test.go | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 x/emissions/keeper/withdrable_emissions_test.go diff --git a/changelog.md b/changelog.md index 857314ebea..30d04b7675 100644 --- a/changelog.md +++ b/changelog.md @@ -21,6 +21,7 @@ ### Features * [1698](https://github.com/zeta-chain/node/issues/1698) - bitcoin dynamic depositor fee +* [1811](https://github.com/zeta-chain/node/pull/1811) - add a message to withdraw emission rewards ### Docs diff --git a/x/emissions/keeper/withdrable_emissions_test.go b/x/emissions/keeper/withdrable_emissions_test.go new file mode 100644 index 0000000000..efc58f72ea --- /dev/null +++ b/x/emissions/keeper/withdrable_emissions_test.go @@ -0,0 +1,104 @@ +package keeper_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func Test_WithdrawableEmissions(t *testing.T) { + t.Run("set valid withdrawable emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, we, we2) + }) + + t.Run("get all withdrawable emissions", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + withdrawableEmissionslist := make([]emissionstypes.WithdrawableEmissions, 10) + for i := 0; i < 10; i++ { + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + withdrawableEmissionslist[i] = we + } + allWithdrawableEmissions := k.GetAllWithdrawableEmission(ctx) + require.ElementsMatch(t, withdrawableEmissionslist, allWithdrawableEmissions) + }) + + t.Run("add observer emission to an existing value", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + amount := sample.IntInRange(1, 100) + k.AddObserverEmission(ctx, we.Address, amount) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, we.Amount.Add(amount), we2.Amount) + }) + + t.Run("add observer emission to a non-existing value", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + amount := sample.IntInRange(1, 100) + k.AddObserverEmission(ctx, we.Address, amount) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, amount, we2.Amount) + }) + + t.Run("remove observer emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.AddObserverEmission(ctx, we.Address, we.Amount) + + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, we.Amount, we2.Amount) + }) + + t.Run("remove observer emission with not enough emissions available", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + + err := k.RemoveObserverEmission(ctx, we.Address, we.Amount.Add(sdkmath.OneInt())) + require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + }) + + t.Run("SlashObserverEmission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + k.SlashObserverEmission(ctx, we.Address, we.Amount.Sub(sdkmath.OneInt())) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, sdkmath.OneInt(), we2.Amount) + }) + + t.Run("SlashObserverEmission to zero if not enough emissions available", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + k.SlashObserverEmission(ctx, we.Address, we.Amount.Add(sdkmath.OneInt())) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, sdkmath.ZeroInt(), we2.Amount) + }) + + t.Run("try slashing non existing observer emission", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + address := sample.AccAddress() + k.SlashObserverEmission(ctx, address, sdkmath.OneInt()) + we, found := k.GetWithdrawableEmission(ctx, address) + require.True(t, found) + require.Equal(t, sdkmath.ZeroInt(), we.Amount) + }) + +} From da7f1a6376a6dfdbec6c64f2508fdc259eb867a4 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 27 Feb 2024 02:32:20 -0500 Subject: [PATCH 04/20] fix unit tests for withdrawable emissions --- .../keeper/withdrable_emissions_test.go | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/x/emissions/keeper/withdrable_emissions_test.go b/x/emissions/keeper/withdrable_emissions_test.go index efc58f72ea..45f03a080c 100644 --- a/x/emissions/keeper/withdrable_emissions_test.go +++ b/x/emissions/keeper/withdrable_emissions_test.go @@ -56,11 +56,14 @@ func Test_WithdrawableEmissions(t *testing.T) { t.Run("remove observer emission", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) - k.AddObserverEmission(ctx, we.Address, we.Amount) + k.SetWithdrawableEmission(ctx, we) + + err := k.RemoveObserverEmission(ctx, we.Address, we.Amount) + require.NoError(t, err) we2, found := k.GetWithdrawableEmission(ctx, we.Address) require.True(t, found) - require.Equal(t, we.Amount, we2.Amount) + require.Equal(t, sdkmath.ZeroInt(), we2.Amount) }) t.Run("remove observer emission with not enough emissions available", func(t *testing.T) { @@ -72,7 +75,17 @@ func Test_WithdrawableEmissions(t *testing.T) { require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) }) - t.Run("SlashObserverEmission", func(t *testing.T) { + t.Run("try remove non-existent emission ", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + address := sample.AccAddress() + err := k.RemoveObserverEmission(ctx, address, sdkmath.ZeroInt()) + require.NoError(t, err) + we, found := k.GetWithdrawableEmission(ctx, address) + require.True(t, found) + require.Equal(t, sdkmath.ZeroInt(), we.Amount) + }) + + t.Run("slash observer emission", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, we) @@ -82,7 +95,7 @@ func Test_WithdrawableEmissions(t *testing.T) { require.Equal(t, sdkmath.OneInt(), we2.Amount) }) - t.Run("SlashObserverEmission to zero if not enough emissions available", func(t *testing.T) { + t.Run("slash observer emission to zero if not enough emissions available", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, we) From dca649eaf68357e2dcb068a30304b17172d68426 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 27 Feb 2024 20:16:04 -0500 Subject: [PATCH 05/20] generate files --- docs/openapi/openapi.swagger.yaml | 2 + docs/spec/emissions/messages.md | 17 +++ proto/emissions/withdrawable_emissions.proto | 1 - testutil/keeper/keeper.go | 2 +- testutil/keeper/mocks/emissions/emission.go | 24 ++++ typescript/emissions/index.d.ts | 1 + typescript/emissions/tx_pb.d.ts | 56 ++++++++ .../emissions/withdrawable_emissions_pb.d.ts | 29 +++++ x/emissions/abci.go | 12 +- .../keeper/msg_server_withdraw_emissions.go | 24 +++- .../msg_server_withdraw_emissions_test.go | 121 ++++++++++++++++++ x/emissions/keeper/withdraw_emissions.go | 2 +- ...ons_test.go => withdraw_emissions_test.go} | 4 +- ...test.go => withdrawable_emissions_test.go} | 0 x/emissions/types/errors.go | 9 +- .../types/message_withdraw_emissons_test.go | 12 +- .../types/withdrawable_emissions.pb.go | 71 ++-------- 17 files changed, 308 insertions(+), 79 deletions(-) create mode 100644 docs/spec/emissions/messages.md create mode 100644 testutil/keeper/mocks/emissions/emission.go create mode 100644 typescript/emissions/tx_pb.d.ts create mode 100644 x/emissions/keeper/msg_server_withdraw_emissions_test.go rename x/emissions/keeper/{withdraw_emmsions_test.go => withdraw_emissions_test.go} (96%) rename x/emissions/keeper/{withdrable_emissions_test.go => withdrawable_emissions_test.go} (100%) diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index a2108f73b1..deda692c08 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -53964,6 +53964,8 @@ definitions: type: string proved: type: boolean + emissionsMsgWithdrawEmissionResponse: + type: object emissionsQueryGetEmissionsFactorsResponse: type: object properties: diff --git a/docs/spec/emissions/messages.md b/docs/spec/emissions/messages.md new file mode 100644 index 0000000000..08b1e89d1f --- /dev/null +++ b/docs/spec/emissions/messages.md @@ -0,0 +1,17 @@ +# Messages + +## MsgWithdrawEmission + +WithdrawEmission create a withdraw emission object , which is then process at endblock +The withdraw emission object is created and stored +using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed. +Creating a withdraw does not guarantee that the emission will be processed +All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not. + +```proto +message MsgWithdrawEmission { + string creator = 1; + string amount = 2; +} +``` + diff --git a/proto/emissions/withdrawable_emissions.proto b/proto/emissions/withdrawable_emissions.proto index 5b97b903a3..aa90cb1387 100644 --- a/proto/emissions/withdrawable_emissions.proto +++ b/proto/emissions/withdrawable_emissions.proto @@ -19,5 +19,4 @@ message WithdrawEmission { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; - string withdraw_failed_reason = 4; } diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index a5e9e8851c..b9b5eab3c9 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -106,7 +106,7 @@ var moduleAccountPerms = map[string][]string{ crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, emissionstypes.ModuleName: {authtypes.Minter}, - emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter}, + emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter, authtypes.Burner}, emissionstypes.UndistributedTssRewardsPool: nil, } diff --git a/testutil/keeper/mocks/emissions/emission.go b/testutil/keeper/mocks/emissions/emission.go new file mode 100644 index 0000000000..6bc969acd8 --- /dev/null +++ b/testutil/keeper/mocks/emissions/emission.go @@ -0,0 +1,24 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// EmissionKeeper is an autogenerated mock type for the EmissionKeeper type +type EmissionKeeper struct { + mock.Mock +} + +// NewEmissionKeeper creates a new instance of EmissionKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionKeeper { + mock := &EmissionKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/typescript/emissions/index.d.ts b/typescript/emissions/index.d.ts index 81e140bbf8..54f80adbc5 100644 --- a/typescript/emissions/index.d.ts +++ b/typescript/emissions/index.d.ts @@ -2,4 +2,5 @@ export * from "./events_pb"; export * from "./genesis_pb"; export * from "./params_pb"; export * from "./query_pb"; +export * from "./tx_pb"; export * from "./withdrawable_emissions_pb"; diff --git a/typescript/emissions/tx_pb.d.ts b/typescript/emissions/tx_pb.d.ts new file mode 100644 index 0000000000..e8ed75ab15 --- /dev/null +++ b/typescript/emissions/tx_pb.d.ts @@ -0,0 +1,56 @@ +// @generated by protoc-gen-es v1.3.0 with parameter "target=dts" +// @generated from file emissions/tx.proto (package zetachain.zetacore.emissions, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; + +/** + * @generated from message zetachain.zetacore.emissions.MsgWithdrawEmission + */ +export declare class MsgWithdrawEmission extends Message { + /** + * @generated from field: string creator = 1; + */ + creator: string; + + /** + * @generated from field: string amount = 2; + */ + amount: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmission"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgWithdrawEmission; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgWithdrawEmission; + + static fromJsonString(jsonString: string, options?: Partial): MsgWithdrawEmission; + + static equals(a: MsgWithdrawEmission | PlainMessage | undefined, b: MsgWithdrawEmission | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.emissions.MsgWithdrawEmissionResponse + */ +export declare class MsgWithdrawEmissionResponse extends Message { + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmissionResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgWithdrawEmissionResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgWithdrawEmissionResponse; + + static fromJsonString(jsonString: string, options?: Partial): MsgWithdrawEmissionResponse; + + static equals(a: MsgWithdrawEmissionResponse | PlainMessage | undefined, b: MsgWithdrawEmissionResponse | PlainMessage | undefined): boolean; +} + diff --git a/typescript/emissions/withdrawable_emissions_pb.d.ts b/typescript/emissions/withdrawable_emissions_pb.d.ts index 6b85546b5d..46d0d4f4f9 100644 --- a/typescript/emissions/withdrawable_emissions_pb.d.ts +++ b/typescript/emissions/withdrawable_emissions_pb.d.ts @@ -35,3 +35,32 @@ export declare class WithdrawableEmissions extends Message | undefined, b: WithdrawableEmissions | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.emissions.WithdrawEmission + */ +export declare class WithdrawEmission extends Message { + /** + * @generated from field: string address = 1; + */ + address: string; + + /** + * @generated from field: string amount = 2; + */ + amount: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.emissions.WithdrawEmission"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): WithdrawEmission; + + static fromJson(jsonValue: JsonValue, options?: Partial): WithdrawEmission; + + static fromJsonString(jsonString: string, options?: Partial): WithdrawEmission; + + static equals(a: WithdrawEmission | PlainMessage | undefined, b: WithdrawEmission | PlainMessage | undefined): boolean; +} + diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 7a3285ffb2..5f87df5f9d 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -149,27 +149,35 @@ func DistributeTssRewards(ctx sdk.Context, amount sdk.Int, bankKeeper types.Bank func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) { allWithdrawEmissions := keeper.GetAllWithdrawEmissions(ctx) + // delete all withdraw emissions irrespective of weather they are processed or not; this is to avoid processing the same withdraw emission again if in case it fails + // if a witdraw emission fails, it will need to be created again by the observer defer func() { for _, withdrawEmission := range allWithdrawEmissions { keeper.DeleteWithdrawEmissions(ctx, withdrawEmission.Address) } }() for _, withdrawEmission := range allWithdrawEmissions { + // use a tmpCtx, which is a cache-wrapped context to avoid writing to the store if the transfer of funds fail tmpCtx, commit := ctx.CacheContext() coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawEmission.Amount)) + + // parse the address if it fails to log the error and continue to the next withdraw address, err := sdk.AccAddressFromBech32(withdrawEmission.Address) if err != nil { ctx.Logger().Error(fmt.Sprintf("Error while parsing withdraw emission address %s : %s", withdrawEmission.Address, err)) continue } + + // remove the withdraw emission amount from the observer and send it to the observer + // RemoveObserverEmission checks weather the observer has enough withdrawable emissions available err = keeper.RemoveObserverEmission(tmpCtx, withdrawEmission.Address, withdrawEmission.Amount) if err != nil { - ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission from observer %s : %s", withdrawEmission.Address, err)) + ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission amount from observer %s : %s", withdrawEmission.Address, err)) continue } err = keeper.GetBankKeeper().SendCoinsFromModuleToAccount(tmpCtx, types.UndistributedObserverRewardsPool, address, coin) if err != nil { - ctx.Logger().Error(fmt.Sprintf("Error while sending withdraw emission to %s : %s", withdrawEmission.Address, err)) + ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to %s : %s", withdrawEmission.Address, err)) continue } commit() diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 31b4a90f9c..04a96300ef 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -5,12 +5,34 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/x/emissions/types" ) +// WithdrawEmission create a withdraw emission object , which is then process at endblock +// The withdraw emission object is created and stored +// using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed. +// Creating a withdraw does not guarantee that the emission will be processed +// All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not. func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdrawEmission) (*types.MsgWithdrawEmissionResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - err := k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount) + + // check if the creator address is valid + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) + } + + // check if undistributed rewards pool has enough balance to process this request. + // This is just a basic check , the actual processing at endblock might still fail if the pool balance gets affected . + undistributedRewardsBalanced := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) + if undistributedRewardsBalanced.Amount.LT(msg.Amount) { + return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request") + } + + // create a withdraw emission object + // CreateWithdrawEmissions makes sure that enough withdrawable emissions are available before creating the withdraw object + err = k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount) if err != nil { return nil, errorsmod.Wrap(types.ErrUnableToCreateWithdrawEmissions, err.Error()) } diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go new file mode 100644 index 0000000000..a164b1a442 --- /dev/null +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -0,0 +1,121 @@ +package keeper_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/emissions" + "github.com/zeta-chain/zetacore/x/emissions/keeper" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestMsgServer_WithdrawEmission(t *testing.T) { + t.Run("successfully withdraw emissions at endblock", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + + msgServer := keeper.NewMsgServerImpl(*k) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + + _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: withdrawableEmission.Address, + Amount: withdrawableEmission.Amount, + }) + require.NoError(t, err) + + we, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) + require.True(t, found) + require.Equal(t, withdrawableEmission.Amount, we.Amount) + + balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() + require.Equal(t, sdk.ZeroInt().String(), balance) + + emissions.EndBlocker(ctx, *k) + balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() + require.Equal(t, withdrawableEmission.Amount.String(), balance) + }) + + t.Run("unable to create withdraw emissions with invalid address", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + + msgServer := keeper.NewMsgServerImpl(*k) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + + _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: "invalid_address", + Amount: withdrawableEmission.Amount, + }) + require.ErrorIs(t, err, types.ErrInvalidAddress) + }) + + t.Run("unable to create withdraw emissions if undistributed bbserver rewards pool does not have enough balance", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + + msgServer := keeper.NewMsgServerImpl(*k) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + + _, err := msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: withdrawableEmission.Address, + Amount: withdrawableEmission.Amount, + }) + require.ErrorIs(t, err, types.ErrRewardsPoolDoesNotHaveEnoughBalance) + }) + + t.Run("unable to create withdraw emissions with invalid amount", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + msgServer := keeper.NewMsgServerImpl(*k) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + _, err := msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: withdrawableEmission.Address, + Amount: sdkmath.NewInt(-1), + }) + require.ErrorIs(t, err, types.ErrUnableToCreateWithdrawEmissions) + }) + + t.Run("successfully create withdraw emissions but unable to process it", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + msgServer := keeper.NewMsgServerImpl(*k) + withdrawablEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawablEmission) + err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount))) + require.NoError(t, err) + + _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: withdrawablEmission.Address, + Amount: withdrawablEmission.Amount, + }) + require.NoError(t, err) + + we, found := k.GetWithdrawEmissions(ctx, withdrawablEmission.Address) + require.True(t, found) + require.Equal(t, withdrawablEmission.Amount, we.Amount) + + balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String() + require.Equal(t, sdk.ZeroInt().String(), balance) + + // Undistributed pool balance gets affected after the withdraw has been created + err = sk.BankKeeper.BurnCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount))) + require.NoError(t, err) + + emissions.EndBlocker(ctx, *k) + // Undistributed pool does not have a balance so no rewards are distributed + balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String() + require.Equal(t, sdk.ZeroInt().String(), balance) + + // Withdraw gets deleted after end-blocker + _, found = k.GetWithdrawEmissions(ctx, withdrawablEmission.Address) + require.False(t, found) + }) +} diff --git a/x/emissions/keeper/withdraw_emissions.go b/x/emissions/keeper/withdraw_emissions.go index d481f2aed0..4e3ad789ab 100644 --- a/x/emissions/keeper/withdraw_emissions.go +++ b/x/emissions/keeper/withdraw_emissions.go @@ -46,7 +46,7 @@ func (k Keeper) CreateWithdrawEmissions(ctx sdk.Context, address string, amount return types.ErrEmissionsNotFound } if amount.IsNegative() || amount.IsZero() { - return types.ErrNotEnoughEmissionsAvailable + return types.ErrInvalidAmount } if amount.GT(emissions.Amount) { amount = emissions.Amount diff --git a/x/emissions/keeper/withdraw_emmsions_test.go b/x/emissions/keeper/withdraw_emissions_test.go similarity index 96% rename from x/emissions/keeper/withdraw_emmsions_test.go rename to x/emissions/keeper/withdraw_emissions_test.go index 5d0c05754f..91129e9a57 100644 --- a/x/emissions/keeper/withdraw_emmsions_test.go +++ b/x/emissions/keeper/withdraw_emissions_test.go @@ -87,7 +87,7 @@ func TestKeeper_CreateWithdrawEmissions(t *testing.T) { withdrawableEmission.Amount = sdkmath.ZeroInt() k.SetWithdrawableEmission(ctx, withdrawableEmission) err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.ZeroInt()) - require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) }) t.Run("unable to create withdraw for negative amount", func(t *testing.T) { @@ -96,7 +96,7 @@ func TestKeeper_CreateWithdrawEmissions(t *testing.T) { withdrawableEmission.Amount = sdkmath.NewInt(-1) k.SetWithdrawableEmission(ctx, withdrawableEmission) err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.NewInt(-1)) - require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) }) t.Run("unable to create withdraw for non existing withdrawable emission", func(t *testing.T) { diff --git a/x/emissions/keeper/withdrable_emissions_test.go b/x/emissions/keeper/withdrawable_emissions_test.go similarity index 100% rename from x/emissions/keeper/withdrable_emissions_test.go rename to x/emissions/keeper/withdrawable_emissions_test.go diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index 7e5bbf490c..f12b2365df 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -5,8 +5,11 @@ import errorsmod "cosmossdk.io/errors" // DONTCOVER var ( - ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") + ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") + ErrNotEnoughEmissionsAvailable = errorsmod.Register(ModuleName, 1001, "Not enough emissions available to withdraw") + ErrUnableToCreateWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to create withdraw emissions") + ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "Invalid address") + ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "Rewards pool does not have enough balance") - ErrNotEnoughEmissionsAvailable = errorsmod.Register(ModuleName, 1001, "Not enough emissions available to withdraw") - ErrUnableToCreateWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to create withdraw emissions") + ErrInvalidAmount = errorsmod.Register(ModuleName, 1005, "Invalid amount") ) diff --git a/x/emissions/types/message_withdraw_emissons_test.go b/x/emissions/types/message_withdraw_emissons_test.go index 26872beefb..ad8f632188 100644 --- a/x/emissions/types/message_withdraw_emissons_test.go +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -16,15 +16,15 @@ func TestMsgWithdrawEmission_ValidateBasic(t *testing.T) { require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) }) - t.Run("valid withdraw message", func(t *testing.T) { - msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) - err := msg.ValidateBasic() - require.NoError(t, err) - }) - t.Run("invalid amount", func(t *testing.T) { msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(-100, -1)) err := msg.ValidateBasic() require.ErrorIs(t, err, sdkerrors.ErrInvalidCoins) }) + + t.Run("valid withdraw message", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) + err := msg.ValidateBasic() + require.NoError(t, err) + }) } diff --git a/x/emissions/types/withdrawable_emissions.pb.go b/x/emissions/types/withdrawable_emissions.pb.go index b8c49d770c..445f6e46a2 100644 --- a/x/emissions/types/withdrawable_emissions.pb.go +++ b/x/emissions/types/withdrawable_emissions.pb.go @@ -71,9 +71,8 @@ func (m *WithdrawableEmissions) GetAddress() string { } type WithdrawEmission struct { - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` - WithdrawFailedReason string `protobuf:"bytes,4,opt,name=withdraw_failed_reason,json=withdrawFailedReason,proto3" json:"withdraw_failed_reason,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` } func (m *WithdrawEmission) Reset() { *m = WithdrawEmission{} } @@ -116,13 +115,6 @@ func (m *WithdrawEmission) GetAddress() string { return "" } -func (m *WithdrawEmission) GetWithdrawFailedReason() string { - if m != nil { - return m.WithdrawFailedReason - } - return "" -} - func init() { proto.RegisterType((*WithdrawableEmissions)(nil), "zetachain.zetacore.emissions.WithdrawableEmissions") proto.RegisterType((*WithdrawEmission)(nil), "zetachain.zetacore.emissions.WithdrawEmission") @@ -133,7 +125,7 @@ func init() { } var fileDescriptor_56e0acf72be654f9 = []byte{ - // 278 bytes of a gzipped FileDescriptorProto + // 244 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4b, 0xcd, 0xcd, 0x2c, 0x2e, 0xce, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x4f, 0x4c, 0xca, 0x49, 0x8d, 0x87, 0x0b, 0xeb, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xc9, 0x54, 0xa5, 0x96, @@ -144,14 +136,12 @@ var fileDescriptor_56e0acf72be654f9 = []byte{ 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x13, 0x48, 0xc2, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, 0xe4, 0xd5, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xa1, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, - 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0xd2, 0x2a, 0x46, 0x2e, 0x01, 0x98, 0xdd, 0x30, 0x7b, 0x69, - 0x6f, 0xad, 0x90, 0x09, 0x97, 0x18, 0x2c, 0x14, 0xe3, 0xd3, 0x12, 0x33, 0x73, 0x52, 0x53, 0xe2, - 0x8b, 0x52, 0x13, 0x8b, 0xf3, 0xf3, 0x24, 0x58, 0xc0, 0x16, 0x8a, 0xc0, 0x64, 0xdd, 0xc0, 0x92, - 0x41, 0x60, 0x39, 0x27, 0xaf, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, - 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, - 0x40, 0xb2, 0x1f, 0x14, 0xec, 0xba, 0xe0, 0x18, 0xd0, 0x87, 0xc5, 0x80, 0x7e, 0x85, 0x3e, 0x22, - 0xfa, 0xc0, 0xae, 0x49, 0x62, 0x03, 0x07, 0xbd, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x53, 0x9f, - 0x6e, 0xbe, 0xd8, 0x01, 0x00, 0x00, + 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0x52, 0x09, 0x97, 0x00, 0xcc, 0x6a, 0x98, 0xb5, 0xb4, 0xb7, + 0xd5, 0xc9, 0xeb, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, + 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0x90, 0x4c, + 0x02, 0x85, 0x9f, 0x2e, 0x38, 0x28, 0xf5, 0x61, 0x41, 0xa9, 0x5f, 0xa1, 0x8f, 0x88, 0x07, 0xb0, + 0xb9, 0x49, 0x6c, 0xe0, 0x30, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x95, 0x64, 0x28, + 0xa1, 0x01, 0x00, 0x00, } func (m *WithdrawableEmissions) Marshal() (dAtA []byte, err error) { @@ -214,13 +204,6 @@ func (m *WithdrawEmission) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.WithdrawFailedReason) > 0 { - i -= len(m.WithdrawFailedReason) - copy(dAtA[i:], m.WithdrawFailedReason) - i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(len(m.WithdrawFailedReason))) - i-- - dAtA[i] = 0x22 - } { size := m.Amount.Size() i -= size @@ -279,10 +262,6 @@ func (m *WithdrawEmission) Size() (n int) { } l = m.Amount.Size() n += 1 + l + sovWithdrawableEmissions(uint64(l)) - l = len(m.WithdrawFailedReason) - if l > 0 { - n += 1 + l + sovWithdrawableEmissions(uint64(l)) - } return n } @@ -503,38 +482,6 @@ func (m *WithdrawEmission) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WithdrawFailedReason", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowWithdrawableEmissions - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.WithdrawFailedReason = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipWithdrawableEmissions(dAtA[iNdEx:]) From 6dff9a37a858208520acae335c49353647499c38 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 27 Feb 2024 21:42:03 -0500 Subject: [PATCH 06/20] fix typo --- x/emissions/keeper/msg_server_withdraw_emissions.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 04a96300ef..6632582259 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -23,10 +23,10 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) } - // check if undistributed rewards pool has enough balance to process this request. - // This is just a basic check , the actual processing at endblock might still fail if the pool balance gets affected . - undistributedRewardsBalanced := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) - if undistributedRewardsBalanced.Amount.LT(msg.Amount) { + // check if the undistributed rewards pool has enough balance to process this request. + // This is just a preliminary check, the actual processing at endblock might still fail if the pool balance gets affected. + undistributedRewardsBalance := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) + if undistributedRewardsBalance.Amount.LT(msg.Amount) { return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request") } From c06b9eb0410dd08cfd4dfb57a04c8f7d7bf1387e Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 1 Mar 2024 16:37:28 -0500 Subject: [PATCH 07/20] simply to withdrawing emission amount directly in a message --- proto/emissions/withdrawable_emissions.proto | 8 - testutil/sample/emissions.go | 10 - x/emissions/abci.go | 37 --- x/emissions/abci_test.go | 124 ---------- x/emissions/client/cli/tx.go | 2 +- .../client/cli/tx_create_withdraw_emssions.go | 2 +- .../keeper/msg_server_withdraw_emissions.go | 14 +- .../msg_server_withdraw_emissions_test.go | 49 +--- x/emissions/keeper/withdraw_emissions.go | 59 ----- x/emissions/keeper/withdraw_emissions_test.go | 107 -------- x/emissions/keeper/withdrawable_emissions.go | 17 +- .../keeper/withdrawable_emissions_test.go | 95 ++++++-- x/emissions/module.go | 3 +- x/emissions/types/errors.go | 2 +- .../types/withdrawable_emissions.pb.go | 230 +----------------- 15 files changed, 113 insertions(+), 646 deletions(-) delete mode 100644 x/emissions/keeper/withdraw_emissions.go delete mode 100644 x/emissions/keeper/withdraw_emissions_test.go diff --git a/proto/emissions/withdrawable_emissions.proto b/proto/emissions/withdrawable_emissions.proto index aa90cb1387..9af2d5569b 100644 --- a/proto/emissions/withdrawable_emissions.proto +++ b/proto/emissions/withdrawable_emissions.proto @@ -12,11 +12,3 @@ message WithdrawableEmissions { (gogoproto.nullable) = false ]; } - -message WithdrawEmission { - string address = 1; - string amount = 2 [ - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.nullable) = false - ]; -} diff --git a/testutil/sample/emissions.go b/testutil/sample/emissions.go index 9e5c39e8af..491f16e501 100644 --- a/testutil/sample/emissions.go +++ b/testutil/sample/emissions.go @@ -16,13 +16,3 @@ func WithdrawableEmissions(t *testing.T) types.WithdrawableEmissions { Amount: math.NewInt(r.Int63()), } } - -func WithdrawEmission(t *testing.T) types.WithdrawEmission { - addr := AccAddress() - r := newRandFromStringSeed(t, addr) - - return types.WithdrawEmission{ - Address: AccAddress(), - Amount: math.NewInt(r.Int63()), - } -} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 5f87df5f9d..2fb0561bb6 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -146,40 +146,3 @@ func DistributeTssRewards(ctx sdk.Context, amount sdk.Int, bankKeeper types.Bank coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, amount)) return bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.UndistributedTssRewardsPool, coin) } - -func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) { - allWithdrawEmissions := keeper.GetAllWithdrawEmissions(ctx) - // delete all withdraw emissions irrespective of weather they are processed or not; this is to avoid processing the same withdraw emission again if in case it fails - // if a witdraw emission fails, it will need to be created again by the observer - defer func() { - for _, withdrawEmission := range allWithdrawEmissions { - keeper.DeleteWithdrawEmissions(ctx, withdrawEmission.Address) - } - }() - for _, withdrawEmission := range allWithdrawEmissions { - // use a tmpCtx, which is a cache-wrapped context to avoid writing to the store if the transfer of funds fail - tmpCtx, commit := ctx.CacheContext() - coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawEmission.Amount)) - - // parse the address if it fails to log the error and continue to the next withdraw - address, err := sdk.AccAddressFromBech32(withdrawEmission.Address) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("Error while parsing withdraw emission address %s : %s", withdrawEmission.Address, err)) - continue - } - - // remove the withdraw emission amount from the observer and send it to the observer - // RemoveObserverEmission checks weather the observer has enough withdrawable emissions available - err = keeper.RemoveObserverEmission(tmpCtx, withdrawEmission.Address, withdrawEmission.Amount) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission amount from observer %s : %s", withdrawEmission.Address, err)) - continue - } - err = keeper.GetBankKeeper().SendCoinsFromModuleToAccount(tmpCtx, types.UndistributedObserverRewardsPool, address, coin) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to %s : %s", withdrawEmission.Address, err)) - continue - } - commit() - } -} diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index 247a682879..55e6188ba9 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -16,130 +16,6 @@ import ( observerTypes "github.com/zeta-chain/zetacore/x/observer/types" ) -func TestEndBlocker(t *testing.T) { - t.Run("successfully process all emissons created in a block", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - totalAmount := sdkmath.ZeroInt() - emissions := make([]emissionstypes.WithdrawEmission, 10) - - for i := 0; i < 10; i++ { - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ - Address: emission.Address, - Amount: emission.Amount, - }) - emissions[i] = emission - totalAmount = totalAmount.Add(emission.Amount) - } - - err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalAmount))) - require.NoError(t, err) - - require.ElementsMatch(t, emissions, k.GetAllWithdrawEmissions(ctx)) - emissionsModule.EndBlocker(ctx, *k) - require.Empty(t, k.GetAllWithdrawEmissions(ctx)) - for _, emission := range emissions { - address, err := sdk.AccAddressFromBech32(emission.Address) - require.NoError(t, err) - balance := k.GetBankKeeper().GetBalance(ctx, address, config.BaseDenom) - require.Equal(t, emission.Amount.String(), balance.Amount.String()) - } - }) - - t.Run("do not commit unsuccessful withdraws to state", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() - - totalAmount := sdkmath.ZeroInt() - emissions := make([]emissionstypes.WithdrawEmission, 10) - unsuccessfulIndex := 5 - for i := 0; i < 10; i++ { - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - emissions[i] = emission - totalAmount = totalAmount.Add(emission.Amount) - if i == unsuccessfulIndex { - continue - } - k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ - Address: emission.Address, - Amount: emission.Amount, - }) - } - - err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalAmount))) - require.NoError(t, err) - - require.ElementsMatch(t, emissions, k.GetAllWithdrawEmissions(ctx)) - emissionsModule.EndBlocker(ctx, *k) - require.Empty(t, k.GetAllWithdrawEmissions(ctx)) - for i, emission := range emissions { - address, err := sdk.AccAddressFromBech32(emission.Address) - require.NoError(t, err) - balance := k.GetBankKeeper().GetBalance(ctx, address, config.BaseDenom) - if i == unsuccessfulIndex { - require.Equal(t, sdkmath.ZeroInt().String(), balance.Amount.String()) - continue - } - require.Equal(t, emission.Amount.String(), balance.Amount.String()) - } - observerPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount - require.Equal(t, emissions[unsuccessfulIndex].Amount.String(), observerPoolBalances.String()) - }) - - t.Run("unable to process withdraw if address is invalid", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() - - emission := sample.WithdrawEmission(t) - emission.Address = "invalid_address" - k.SetWithdrawEmissions(ctx, emission) - k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ - Address: emission.Address, - Amount: emission.Amount, - }) - - err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emission.Amount))) - require.NoError(t, err) - emissionsModule.EndBlocker(ctx, *k) - require.Empty(t, k.GetAllWithdrawEmissions(ctx)) - require.Equal(t, emission.Amount.String(), sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount.String()) - }) - - t.Run("unable to process withdraw if amount is more than available emission", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() - - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ - Address: emission.Address, - Amount: emission.Amount.Sub(sdkmath.NewInt(1)), - }) - - err := sk.BankKeeper.MintCoins(ctx, emissionstypes.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emission.Amount))) - require.NoError(t, err) - emissionsModule.EndBlocker(ctx, *k) - require.Empty(t, k.GetAllWithdrawEmissions(ctx)) - require.Equal(t, emission.Amount.String(), sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount.String()) - }) - - t.Run("unable to process withdraw if UndistributedObserverRewardsPool does not have enough balance", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ - Address: emission.Address, - Amount: emission.Amount, - }) - - emissionsModule.EndBlocker(ctx, *k) - require.Empty(t, k.GetAllWithdrawEmissions(ctx)) - require.Equal(t, sdkmath.ZeroInt().String(), sk.BankKeeper.GetBalance(ctx, sdk.MustAccAddressFromBech32(emission.Address), config.BaseDenom).Amount.String()) - }) -} func TestBeginBlocker(t *testing.T) { t.Run("no observer distribution happens if emissions module account is empty", func(t *testing.T) { k, ctx, _, zk := keepertest.EmissionsKeeper(t) diff --git a/x/emissions/client/cli/tx.go b/x/emissions/client/cli/tx.go index 8e1fe8632e..78bcee33b5 100644 --- a/x/emissions/client/cli/tx.go +++ b/x/emissions/client/cli/tx.go @@ -18,6 +18,6 @@ func GetTxCmd() *cobra.Command { SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } - + cmd.AddCommand(CmdCreateWithdrawEmission()) return cmd } diff --git a/x/emissions/client/cli/tx_create_withdraw_emssions.go b/x/emissions/client/cli/tx_create_withdraw_emssions.go index c86f29ade3..a3a60fb406 100644 --- a/x/emissions/client/cli/tx_create_withdraw_emssions.go +++ b/x/emissions/client/cli/tx_create_withdraw_emssions.go @@ -13,7 +13,7 @@ import ( func CmdCreateWithdrawEmission() *cobra.Command { cmd := &cobra.Command{ - Use: "create-withdraw-emission", + Use: "create-withdraw-emission [amount]", Short: "create a new withdrawEmission", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 6632582259..25703de0c0 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,7 +19,7 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra ctx := sdk.UnwrapSDKContext(goCtx) // check if the creator address is valid - _, err := sdk.AccAddressFromBech32(msg.Creator) + address, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) } @@ -30,11 +31,14 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request") } - // create a withdraw emission object - // CreateWithdrawEmissions makes sure that enough withdrawable emissions are available before creating the withdraw object - err = k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount) + err = k.RemoveWithdrawableEmission(ctx, msg.Creator, msg.Amount) if err != nil { - return nil, errorsmod.Wrap(types.ErrUnableToCreateWithdrawEmissions, err.Error()) + return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error()) + } + err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, msg.Amount))) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to adresss %s for amount %s : err %s", address, msg.Amount, err)) + return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error()) } return &types.MsgWithdrawEmissionResponse{}, nil } diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go index a164b1a442..3fbf4cfda3 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions_test.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -9,13 +9,12 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" - "github.com/zeta-chain/zetacore/x/emissions" "github.com/zeta-chain/zetacore/x/emissions/keeper" "github.com/zeta-chain/zetacore/x/emissions/types" ) func TestMsgServer_WithdrawEmission(t *testing.T) { - t.Run("successfully withdraw emissions at endblock", func(t *testing.T) { + t.Run("successfully withdraw emissions", func(t *testing.T) { k, ctx, sk, _ := keepertest.EmissionsKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) @@ -30,15 +29,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { }) require.NoError(t, err) - we, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) - require.True(t, found) - require.Equal(t, withdrawableEmission.Amount, we.Amount) - balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() - require.Equal(t, sdk.ZeroInt().String(), balance) - - emissions.EndBlocker(ctx, *k) - balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() require.Equal(t, withdrawableEmission.Amount.String(), balance) }) @@ -58,7 +49,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.ErrorIs(t, err, types.ErrInvalidAddress) }) - t.Run("unable to create withdraw emissions if undistributed bbserver rewards pool does not have enough balance", func(t *testing.T) { + t.Run("unable to create withdraw emissions if undistributed rewards pool does not have enough balance", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) @@ -81,41 +72,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { Creator: withdrawableEmission.Address, Amount: sdkmath.NewInt(-1), }) - require.ErrorIs(t, err, types.ErrUnableToCreateWithdrawEmissions) + require.ErrorIs(t, err, types.ErrUnableToWithdrawEmissions) }) - t.Run("successfully create withdraw emissions but unable to process it", func(t *testing.T) { - k, ctx, sk, _ := keepertest.EmissionsKeeper(t) - msgServer := keeper.NewMsgServerImpl(*k) - withdrawablEmission := sample.WithdrawableEmissions(t) - k.SetWithdrawableEmission(ctx, withdrawablEmission) - err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount))) - require.NoError(t, err) - - _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ - Creator: withdrawablEmission.Address, - Amount: withdrawablEmission.Amount, - }) - require.NoError(t, err) - - we, found := k.GetWithdrawEmissions(ctx, withdrawablEmission.Address) - require.True(t, found) - require.Equal(t, withdrawablEmission.Amount, we.Amount) - - balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String() - require.Equal(t, sdk.ZeroInt().String(), balance) - - // Undistributed pool balance gets affected after the withdraw has been created - err = sk.BankKeeper.BurnCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount))) - require.NoError(t, err) - - emissions.EndBlocker(ctx, *k) - // Undistributed pool does not have a balance so no rewards are distributed - balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String() - require.Equal(t, sdk.ZeroInt().String(), balance) - - // Withdraw gets deleted after end-blocker - _, found = k.GetWithdrawEmissions(ctx, withdrawablEmission.Address) - require.False(t, found) - }) } diff --git a/x/emissions/keeper/withdraw_emissions.go b/x/emissions/keeper/withdraw_emissions.go deleted file mode 100644 index 4e3ad789ab..0000000000 --- a/x/emissions/keeper/withdraw_emissions.go +++ /dev/null @@ -1,59 +0,0 @@ -package keeper - -import ( - sdkmath "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/x/emissions/types" -) - -func (k Keeper) SetWithdrawEmissions(ctx sdk.Context, we types.WithdrawEmission) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) - b := k.cdc.MustMarshal(&we) - store.Set([]byte(we.Address), b) -} - -func (k Keeper) GetWithdrawEmissions(ctx sdk.Context, address string) (val types.WithdrawEmission, found bool) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) - b := store.Get(types.KeyPrefix(address)) - if b == nil { - return val, false - } - k.cdc.MustUnmarshal(b, &val) - return val, true -} - -func (k Keeper) DeleteWithdrawEmissions(ctx sdk.Context, address string) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) - store.Delete([]byte(address)) -} - -func (k Keeper) GetAllWithdrawEmissions(ctx sdk.Context) (list []types.WithdrawEmission) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.WithdrawEmissionsKey)) - iterator := sdk.KVStorePrefixIterator(store, []byte{}) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - var val types.WithdrawEmission - k.cdc.MustUnmarshal(iterator.Value(), &val) - list = append(list, val) - } - return -} - -func (k Keeper) CreateWithdrawEmissions(ctx sdk.Context, address string, amount sdkmath.Int) error { - emissions, found := k.GetWithdrawableEmission(ctx, address) - if !found { - return types.ErrEmissionsNotFound - } - if amount.IsNegative() || amount.IsZero() { - return types.ErrInvalidAmount - } - if amount.GT(emissions.Amount) { - amount = emissions.Amount - } - k.SetWithdrawEmissions(ctx, types.WithdrawEmission{ - Address: address, - Amount: amount, - }) - return nil -} diff --git a/x/emissions/keeper/withdraw_emissions_test.go b/x/emissions/keeper/withdraw_emissions_test.go deleted file mode 100644 index 91129e9a57..0000000000 --- a/x/emissions/keeper/withdraw_emissions_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package keeper_test - -import ( - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/stretchr/testify/require" - keepertest "github.com/zeta-chain/zetacore/testutil/keeper" - "github.com/zeta-chain/zetacore/testutil/sample" - emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" -) - -func TestKeeper_WithdrawEmissions(t *testing.T) { - t.Run("set withdraw emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - em, found := k.GetWithdrawEmissions(ctx, emission.Address) - require.True(t, found) - require.Equal(t, emission, em) - }) - - t.Run("replace withdraw emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - oldAmount := emission.Amount - emission.Amount = sample.IntInRange(1, 100) - k.SetWithdrawEmissions(ctx, emission) - em, found := k.GetWithdrawEmissions(ctx, emission.Address) - require.True(t, found) - require.Equal(t, emission, em) - require.NotEqual(t, oldAmount, em.Amount) - }) - t.Run("unable to get withdraw emission which doesnt exist", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - _, found := k.GetWithdrawEmissions(ctx, sample.AccAddress()) - require.False(t, found) - }) - t.Run("delete withdraw emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - k.DeleteWithdrawEmissions(ctx, emission.Address) - _, found := k.GetWithdrawEmissions(ctx, emission.Address) - require.False(t, found) - }) - t.Run("get all withdraw emissions", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - emissions := make([]emissionstypes.WithdrawEmission, 10) - for i := 0; i < 10; i++ { - emission := sample.WithdrawEmission(t) - k.SetWithdrawEmissions(ctx, emission) - emissions[i] = emission - } - allEmissions := k.GetAllWithdrawEmissions(ctx) - require.ElementsMatch(t, emissions, allEmissions) - }) -} - -func TestKeeper_CreateWithdrawEmissions(t *testing.T) { - t.Run("create withdraw emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - withdrawableEmission := sample.WithdrawableEmissions(t) - k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, withdrawableEmission.Amount) - require.NoError(t, err) - em, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) - require.True(t, found) - require.Equal(t, withdrawableEmission.Amount, em.Amount) - }) - - t.Run("create withdraw for max available withdrawable emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - withdrawableEmission := sample.WithdrawableEmissions(t) - k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, withdrawableEmission.Amount.Add(sample.IntInRange(1, 100))) - require.NoError(t, err) - em, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address) - require.True(t, found) - require.Equal(t, withdrawableEmission.Amount, em.Amount) - }) - - t.Run("unable to create withdraw for zero amount", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - withdrawableEmission := sample.WithdrawableEmissions(t) - withdrawableEmission.Amount = sdkmath.ZeroInt() - k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.ZeroInt()) - require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) - }) - - t.Run("unable to create withdraw for negative amount", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - withdrawableEmission := sample.WithdrawableEmissions(t) - withdrawableEmission.Amount = sdkmath.NewInt(-1) - k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.NewInt(-1)) - require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) - }) - - t.Run("unable to create withdraw for non existing withdrawable emission", func(t *testing.T) { - k, ctx, _, _ := keepertest.EmissionsKeeper(t) - err := k.CreateWithdrawEmissions(ctx, sample.AccAddress(), sdkmath.NewInt(1)) - require.ErrorIs(t, err, emissionstypes.ErrEmissionsNotFound) - }) -} diff --git a/x/emissions/keeper/withdrawable_emissions.go b/x/emissions/keeper/withdrawable_emissions.go index 36910be688..a8d4327b2e 100644 --- a/x/emissions/keeper/withdrawable_emissions.go +++ b/x/emissions/keeper/withdrawable_emissions.go @@ -37,6 +37,8 @@ func (k Keeper) GetAllWithdrawableEmission(ctx sdk.Context) (list []types.Withdr return } +// AddObserverEmission adds the given amount to the withdrawable emission of a given address. +// If the address does not have a withdrawable emission, it will create a new withdrawable emission with the given amount. func (k Keeper) AddObserverEmission(ctx sdk.Context, address string, amount sdkmath.Int) { we, found := k.GetWithdrawableEmission(ctx, address) if !found { @@ -46,13 +48,19 @@ func (k Keeper) AddObserverEmission(ctx sdk.Context, address string, amount sdkm k.SetWithdrawableEmission(ctx, we) } -func (k Keeper) RemoveObserverEmission(ctx sdk.Context, address string, amount sdkmath.Int) error { +// RemoveWithdrawableEmission removes the given amount from the withdrawable emission of a given address. +// If the amount is greater than the withdrawable emission, it will remove the entire withdrawable emission. +// If the amount is negative or zero, it will return an error. +func (k Keeper) RemoveWithdrawableEmission(ctx sdk.Context, address string, amount sdkmath.Int) error { we, found := k.GetWithdrawableEmission(ctx, address) if !found { - we = types.WithdrawableEmissions{Address: address, Amount: sdkmath.ZeroInt()} + return types.ErrEmissionsNotFound + } + if amount.IsNegative() || amount.IsZero() { + return types.ErrInvalidAmount } - if we.Amount.LT(amount) { - return types.ErrNotEnoughEmissionsAvailable + if amount.GT(we.Amount) { + amount = we.Amount } we.Amount = we.Amount.Sub(amount) k.SetWithdrawableEmission(ctx, we) @@ -60,6 +68,7 @@ func (k Keeper) RemoveObserverEmission(ctx sdk.Context, address string, amount s } // SlashObserverEmission slashes the rewards of a given address, if the address has no rewards left, it will set the rewards to 0. +// If the address does not have a withdrawable emission, it will create a new withdrawable emission with zero amount. /* This function is a basic implementation of slashing; it will be improved in the future . Improvements will include: - Add a jailing mechanism diff --git a/x/emissions/keeper/withdrawable_emissions_test.go b/x/emissions/keeper/withdrawable_emissions_test.go index 45f03a080c..f81cb1517f 100644 --- a/x/emissions/keeper/withdrawable_emissions_test.go +++ b/x/emissions/keeper/withdrawable_emissions_test.go @@ -53,39 +53,42 @@ func Test_WithdrawableEmissions(t *testing.T) { require.Equal(t, amount, we2.Amount) }) - t.Run("remove observer emission", func(t *testing.T) { + t.Run("slash observer emission", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, we) - - err := k.RemoveObserverEmission(ctx, we.Address, we.Amount) - require.NoError(t, err) - + k.SlashObserverEmission(ctx, we.Address, we.Amount.Sub(sdkmath.OneInt())) we2, found := k.GetWithdrawableEmission(ctx, we.Address) require.True(t, found) - require.Equal(t, sdkmath.ZeroInt(), we2.Amount) + require.Equal(t, sdkmath.OneInt(), we2.Amount) }) - t.Run("remove observer emission with not enough emissions available", func(t *testing.T) { +} +func TestKeeper_AddObserverEmission(t *testing.T) { + t.Run("add observer emission to an existing value", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, we) - - err := k.RemoveObserverEmission(ctx, we.Address, we.Amount.Add(sdkmath.OneInt())) - require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable) + amount := sample.IntInRange(1, 100) + k.AddObserverEmission(ctx, we.Address, amount) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, we.Amount.Add(amount), we2.Amount) }) - t.Run("try remove non-existent emission ", func(t *testing.T) { + t.Run("add observer emission to a non-existing value", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) - address := sample.AccAddress() - err := k.RemoveObserverEmission(ctx, address, sdkmath.ZeroInt()) - require.NoError(t, err) - we, found := k.GetWithdrawableEmission(ctx, address) + we := sample.WithdrawableEmissions(t) + amount := sample.IntInRange(1, 100) + k.AddObserverEmission(ctx, we.Address, amount) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) require.True(t, found) - require.Equal(t, sdkmath.ZeroInt(), we.Amount) + require.Equal(t, amount, we2.Amount) }) +} - t.Run("slash observer emission", func(t *testing.T) { +func TestKeeper_SlashWithdrawableEmission(t *testing.T) { + t.Run("successfully slash withdrawable emission", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) we := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, we) @@ -113,5 +116,63 @@ func Test_WithdrawableEmissions(t *testing.T) { require.True(t, found) require.Equal(t, sdkmath.ZeroInt(), we.Amount) }) +} +func TestKeeper_RemoveObserverEmission(t *testing.T) { + t.Run("remove all observer emission successfully", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + err := k.RemoveWithdrawableEmission(ctx, we.Address, we.Amount) + require.NoError(t, err) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, sdkmath.ZeroInt(), we2.Amount) + }) + + t.Run("remove all observer emission successfully using amount higher that available", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + err := k.RemoveWithdrawableEmission(ctx, we.Address, we.Amount.Add(sdkmath.OneInt())) + require.NoError(t, err) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, sdkmath.ZeroInt(), we2.Amount) + }) + + t.Run("unable to remove non-existent emission ", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + address := sample.AccAddress() + err := k.RemoveWithdrawableEmission(ctx, address, sdkmath.ZeroInt()) + require.Error(t, err, emissionstypes.ErrEmissionsNotFound) + }) + + t.Run("remove all portion of observer emission successfully", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + withdrawAmount := we.Amount.Quo(sdkmath.NewInt(2)) + err := k.RemoveWithdrawableEmission(ctx, we.Address, withdrawAmount) + require.NoError(t, err) + we2, found := k.GetWithdrawableEmission(ctx, we.Address) + require.True(t, found) + require.Equal(t, we.Amount.Sub(withdrawAmount).String(), we2.Amount.String()) + }) + + t.Run("unable to withdraw negative amount", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + err := k.RemoveWithdrawableEmission(ctx, we.Address, sdkmath.NewInt(-1)) + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) + }) + + t.Run("unable to withdraw zero amount", func(t *testing.T) { + k, ctx, _, _ := keepertest.EmissionsKeeper(t) + we := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, we) + err := k.RemoveWithdrawableEmission(ctx, we.Address, sdkmath.ZeroInt()) + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) + }) } diff --git a/x/emissions/module.go b/x/emissions/module.go index 487b951edb..7086c998fc 100644 --- a/x/emissions/module.go +++ b/x/emissions/module.go @@ -174,7 +174,6 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { // EndBlock executes all ABCI EndBlock logic respective to the emissions module. It // returns no validator updates. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - EndBlocker(ctx, am.keeper) +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index f12b2365df..274b2ceec6 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -7,7 +7,7 @@ import errorsmod "cosmossdk.io/errors" var ( ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") ErrNotEnoughEmissionsAvailable = errorsmod.Register(ModuleName, 1001, "Not enough emissions available to withdraw") - ErrUnableToCreateWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to create withdraw emissions") + ErrUnableToWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to withdraw emissions") ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "Invalid address") ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "Rewards pool does not have enough balance") diff --git a/x/emissions/types/withdrawable_emissions.pb.go b/x/emissions/types/withdrawable_emissions.pb.go index 445f6e46a2..b1e0f07e4f 100644 --- a/x/emissions/types/withdrawable_emissions.pb.go +++ b/x/emissions/types/withdrawable_emissions.pb.go @@ -70,54 +70,8 @@ func (m *WithdrawableEmissions) GetAddress() string { return "" } -type WithdrawEmission struct { - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` -} - -func (m *WithdrawEmission) Reset() { *m = WithdrawEmission{} } -func (m *WithdrawEmission) String() string { return proto.CompactTextString(m) } -func (*WithdrawEmission) ProtoMessage() {} -func (*WithdrawEmission) Descriptor() ([]byte, []int) { - return fileDescriptor_56e0acf72be654f9, []int{1} -} -func (m *WithdrawEmission) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *WithdrawEmission) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_WithdrawEmission.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *WithdrawEmission) XXX_Merge(src proto.Message) { - xxx_messageInfo_WithdrawEmission.Merge(m, src) -} -func (m *WithdrawEmission) XXX_Size() int { - return m.Size() -} -func (m *WithdrawEmission) XXX_DiscardUnknown() { - xxx_messageInfo_WithdrawEmission.DiscardUnknown(m) -} - -var xxx_messageInfo_WithdrawEmission proto.InternalMessageInfo - -func (m *WithdrawEmission) GetAddress() string { - if m != nil { - return m.Address - } - return "" -} - func init() { proto.RegisterType((*WithdrawableEmissions)(nil), "zetachain.zetacore.emissions.WithdrawableEmissions") - proto.RegisterType((*WithdrawEmission)(nil), "zetachain.zetacore.emissions.WithdrawEmission") } func init() { @@ -125,7 +79,7 @@ func init() { } var fileDescriptor_56e0acf72be654f9 = []byte{ - // 244 bytes of a gzipped FileDescriptorProto + // 233 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4b, 0xcd, 0xcd, 0x2c, 0x2e, 0xce, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x4f, 0x4c, 0xca, 0x49, 0x8d, 0x87, 0x0b, 0xeb, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0xc9, 0x54, 0xa5, 0x96, @@ -136,12 +90,11 @@ var fileDescriptor_56e0acf72be654f9 = []byte{ 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x13, 0x48, 0xc2, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, 0xe4, 0xd5, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xa1, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, - 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0x52, 0x09, 0x97, 0x00, 0xcc, 0x6a, 0x98, 0xb5, 0xb4, 0xb7, - 0xd5, 0xc9, 0xeb, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, - 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0x90, 0x4c, - 0x02, 0x85, 0x9f, 0x2e, 0x38, 0x28, 0xf5, 0x61, 0x41, 0xa9, 0x5f, 0xa1, 0x8f, 0x88, 0x07, 0xb0, - 0xb9, 0x49, 0x6c, 0xe0, 0x30, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x95, 0x64, 0x28, - 0xa1, 0x01, 0x00, 0x00, + 0x67, 0x5e, 0x49, 0x10, 0x54, 0xb7, 0x93, 0xd7, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, + 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, + 0x31, 0x44, 0x19, 0x20, 0x99, 0x04, 0xf2, 0x89, 0x2e, 0xd8, 0x53, 0xfa, 0x30, 0x4f, 0xe9, 0x57, + 0xe8, 0x23, 0x42, 0x04, 0x6c, 0x6e, 0x12, 0x1b, 0xd8, 0x37, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x99, 0xf1, 0xaf, 0x2c, 0x2b, 0x01, 0x00, 0x00, } func (m *WithdrawableEmissions) Marshal() (dAtA []byte, err error) { @@ -184,46 +137,6 @@ func (m *WithdrawableEmissions) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *WithdrawEmission) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *WithdrawEmission) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *WithdrawEmission) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size := m.Amount.Size() - i -= size - if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Address) > 0 { - i -= len(m.Address) - copy(dAtA[i:], m.Address) - i = encodeVarintWithdrawableEmissions(dAtA, i, uint64(len(m.Address))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func encodeVarintWithdrawableEmissions(dAtA []byte, offset int, v uint64) int { offset -= sovWithdrawableEmissions(v) base := offset @@ -250,21 +163,6 @@ func (m *WithdrawableEmissions) Size() (n int) { return n } -func (m *WithdrawEmission) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Address) - if l > 0 { - n += 1 + l + sovWithdrawableEmissions(uint64(l)) - } - l = m.Amount.Size() - n += 1 + l + sovWithdrawableEmissions(uint64(l)) - return n -} - func sovWithdrawableEmissions(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -387,122 +285,6 @@ func (m *WithdrawableEmissions) Unmarshal(dAtA []byte) error { } return nil } -func (m *WithdrawEmission) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowWithdrawableEmissions - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WithdrawEmission: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WithdrawEmission: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowWithdrawableEmissions - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Address = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowWithdrawableEmissions - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipWithdrawableEmissions(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthWithdrawableEmissions - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipWithdrawableEmissions(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 2d7ab90fb4fb3397ba09eda7b3ea2cc9315eeed9 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 1 Mar 2024 16:38:47 -0500 Subject: [PATCH 08/20] generate docs --- docs/cli/zetacored/zetacored_tx_emissions.md | 1 + ...d_tx_emissions_create-withdraw-emission.md | 52 +++++++++++++++++++ .../emissions/withdrawable_emissions_pb.d.ts | 29 ----------- 3 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md diff --git a/docs/cli/zetacored/zetacored_tx_emissions.md b/docs/cli/zetacored/zetacored_tx_emissions.md index f782c072d1..74996a5e84 100644 --- a/docs/cli/zetacored/zetacored_tx_emissions.md +++ b/docs/cli/zetacored/zetacored_tx_emissions.md @@ -25,4 +25,5 @@ zetacored tx emissions [flags] ### SEE ALSO * [zetacored tx](zetacored_tx.md) - Transactions subcommands +* [zetacored tx emissions create-withdraw-emission](zetacored_tx_emissions_create-withdraw-emission.md) - create a new withdrawEmission diff --git a/docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md b/docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md new file mode 100644 index 0000000000..508dec9118 --- /dev/null +++ b/docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md @@ -0,0 +1,52 @@ +# tx emissions create-withdraw-emission + +create a new withdrawEmission + +``` +zetacored tx emissions create-withdraw-emission [amount] [flags] +``` + +### Options + +``` + -a, --account-number uint The account number of the signing account (offline mode only) + --aux Generate aux signer data instead of sending a tx + -b, --broadcast-mode string Transaction broadcasting mode (sync|async|block) + --dry-run ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible) + --fee-granter string Fee granter grants fees for the transaction + --fee-payer string Fee payer pays fees for the transaction instead of deducting from the signer + --fees string Fees to pay along with transaction; eg: 10uatom + --from string Name or address of private key with which to sign + --gas string gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically. Note: "auto" option doesn't always report accurate results. Set a valid coin value to adjust the result. Can be used instead of "fees". (default 200000) + --gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1) + --gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom) + --generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name) + -h, --help help for create-withdraw-emission + --keyring-backend string Select keyring's backend (os|file|kwallet|pass|test|memory) + --keyring-dir string The client Keyring directory; if omitted, the default 'home' directory will be used + --ledger Use a connected Ledger device + --node string [host]:[port] to tendermint rpc interface for this chain + --note string Note to add a description to the transaction (previously --memo) + --offline Offline mode (does not allow any online functionality) + -o, --output string Output format (text|json) + -s, --sequence uint The sequence number of the signing account (offline mode only) + --sign-mode string Choose sign mode (direct|amino-json|direct-aux), this is an advanced feature + --timeout-height uint Set a block timeout height to prevent the tx from being committed past a certain height + --tip string Tip is the amount that is going to be transferred to the fee payer on the target chain. This flag is only valid when used with --aux, and is ignored if the target chain didn't enable the TipDecorator + -y, --yes Skip tx broadcasting prompt confirmation +``` + +### Options inherited from parent commands + +``` + --chain-id string The network chain ID + --home string directory for config and data + --log_format string The logging format (json|plain) + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) + --trace print out full stack trace on errors +``` + +### SEE ALSO + +* [zetacored tx emissions](zetacored_tx_emissions.md) - emissions transactions subcommands + diff --git a/typescript/emissions/withdrawable_emissions_pb.d.ts b/typescript/emissions/withdrawable_emissions_pb.d.ts index 46d0d4f4f9..6b85546b5d 100644 --- a/typescript/emissions/withdrawable_emissions_pb.d.ts +++ b/typescript/emissions/withdrawable_emissions_pb.d.ts @@ -35,32 +35,3 @@ export declare class WithdrawableEmissions extends Message | undefined, b: WithdrawableEmissions | PlainMessage | undefined): boolean; } -/** - * @generated from message zetachain.zetacore.emissions.WithdrawEmission - */ -export declare class WithdrawEmission extends Message { - /** - * @generated from field: string address = 1; - */ - address: string; - - /** - * @generated from field: string amount = 2; - */ - amount: string; - - constructor(data?: PartialMessage); - - static readonly runtime: typeof proto3; - static readonly typeName = "zetachain.zetacore.emissions.WithdrawEmission"; - static readonly fields: FieldList; - - static fromBinary(bytes: Uint8Array, options?: Partial): WithdrawEmission; - - static fromJson(jsonValue: JsonValue, options?: Partial): WithdrawEmission; - - static fromJsonString(jsonString: string, options?: Partial): WithdrawEmission; - - static equals(a: WithdrawEmission | PlainMessage | undefined, b: WithdrawEmission | PlainMessage | undefined): boolean; -} - From 40aa9a59b6d81ba0371631be0580b84b89fdd1d9 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 1 Mar 2024 16:44:17 -0500 Subject: [PATCH 09/20] reject zero amount withdraws --- x/emissions/types/errors.go | 3 +-- x/emissions/types/message_withdraw_emissions.go | 4 ++-- x/emissions/types/message_withdraw_emissons_test.go | 11 +++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index 274b2ceec6..c25f367391 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -10,6 +10,5 @@ var ( ErrUnableToWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to withdraw emissions") ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "Invalid address") ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "Rewards pool does not have enough balance") - - ErrInvalidAmount = errorsmod.Register(ModuleName, 1005, "Invalid amount") + ErrInvalidAmount = errorsmod.Register(ModuleName, 1005, "Invalid amount") ) diff --git a/x/emissions/types/message_withdraw_emissions.go b/x/emissions/types/message_withdraw_emissions.go index 34d02e8fa1..2d6759b792 100644 --- a/x/emissions/types/message_withdraw_emissions.go +++ b/x/emissions/types/message_withdraw_emissions.go @@ -43,8 +43,8 @@ func (msg *MsgWithdrawEmission) ValidateBasic() error { if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } - if !msg.Amount.GTE(sdkmath.ZeroInt()) { - return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "invalid amount (%s)", msg.Amount.String()) + if !msg.Amount.GT(sdkmath.ZeroInt()) { + return errorsmod.Wrapf(ErrInvalidAmount, "invalid amount (%s)", msg.Amount.String()) } return nil } diff --git a/x/emissions/types/message_withdraw_emissons_test.go b/x/emissions/types/message_withdraw_emissons_test.go index ad8f632188..280999cc3a 100644 --- a/x/emissions/types/message_withdraw_emissons_test.go +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + sdkmath "cosmossdk.io/math" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -16,10 +17,16 @@ func TestMsgWithdrawEmission_ValidateBasic(t *testing.T) { require.ErrorIs(t, err, sdkerrors.ErrInvalidAddress) }) - t.Run("invalid amount", func(t *testing.T) { + t.Run("invalid negative amount", func(t *testing.T) { msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(-100, -1)) err := msg.ValidateBasic() - require.ErrorIs(t, err, sdkerrors.ErrInvalidCoins) + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) + }) + + t.Run("invalid zero amount", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sdkmath.ZeroInt()) + err := msg.ValidateBasic() + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) }) t.Run("valid withdraw message", func(t *testing.T) { From bba6042a9eec88dd68a8f1b2cc53f449ea03a465 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 1 Mar 2024 16:44:58 -0500 Subject: [PATCH 10/20] reject zero amount withdraws --- x/emissions/types/errors.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index c25f367391..e5f778bc25 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -6,7 +6,6 @@ import errorsmod "cosmossdk.io/errors" var ( ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") - ErrNotEnoughEmissionsAvailable = errorsmod.Register(ModuleName, 1001, "Not enough emissions available to withdraw") ErrUnableToWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to withdraw emissions") ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "Invalid address") ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "Rewards pool does not have enough balance") From 25bed840a053fa31e410d6f1d87453ffb6a73921 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Sat, 2 Mar 2024 15:22:33 -0500 Subject: [PATCH 11/20] increase coverage for msg withdraw emissions --- testutil/keeper/emissions.go | 6 +++ .../keeper/msg_server_withdraw_emissions.go | 1 - .../msg_server_withdraw_emissions_test.go | 38 +++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index 988859c762..d016bc3740 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -105,3 +105,9 @@ func EmissionKeeperWithMockOptions( return k, ctx, sdkKeepers, zetaKeepers } + +func GetEmissionsBankMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.EmissionBankKeeper { + cbk, ok := keeper.GetBankKeeper().(*emissionsmocks.EmissionBankKeeper) + require.True(t, ok) + return cbk +} diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 25703de0c0..1fa1287540 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -23,7 +23,6 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra if err != nil { return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) } - // check if the undistributed rewards pool has enough balance to process this request. // This is just a preliminary check, the actual processing at endblock might still fail if the pool balance gets affected. undistributedRewardsBalance := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go index 3fbf4cfda3..f547524b99 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions_test.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -5,6 +5,7 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/cmd/zetacored/config" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" @@ -33,7 +34,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.Equal(t, withdrawableEmission.Amount.String(), balance) }) - t.Run("unable to create withdraw emissions with invalid address", func(t *testing.T) { + t.Run("unable to withdraw emissions with invalid address", func(t *testing.T) { k, ctx, sk, _ := keepertest.EmissionsKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) @@ -49,7 +50,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.ErrorIs(t, err, types.ErrInvalidAddress) }) - t.Run("unable to create withdraw emissions if undistributed rewards pool does not have enough balance", func(t *testing.T) { + t.Run("unable to withdraw emissions if undistributed rewards pool does not have enough balance", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) @@ -63,7 +64,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.ErrorIs(t, err, types.ErrRewardsPoolDoesNotHaveEnoughBalance) }) - t.Run("unable to create withdraw emissions with invalid amount", func(t *testing.T) { + t.Run("unable to withdraw emissions with invalid amount", func(t *testing.T) { k, ctx, _, _ := keepertest.EmissionsKeeper(t) msgServer := keeper.NewMsgServerImpl(*k) withdrawableEmission := sample.WithdrawableEmissions(t) @@ -75,4 +76,35 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.ErrorIs(t, err, types.ErrUnableToWithdrawEmissions) }) + t.Run("unable to withdraw emissions if SendCoinsFromModuleToAccount", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionKeeperWithMockOptions(t, keepertest.EmissionMockOptions{ + UseBankMock: true, + }) + bankMock := keepertest.GetEmissionsBankMock(t, k) + msgServer := keeper.NewMsgServerImpl(*k) + + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + + address, err := sdk.AccAddressFromBech32(withdrawableEmission.Address) + require.NoError(t, err) + + bankMock.On("SendCoinsFromModuleToAccount", + ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))). + Return(types.ErrUnableToWithdrawEmissions).Once() + bankMock.On("GetBalance", + ctx, mock.Anything, config.BaseDenom). + Return(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount), nil).Once() + _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ + Creator: withdrawableEmission.Address, + Amount: withdrawableEmission.Amount, + }) + + require.ErrorIs(t, err, types.ErrUnableToWithdrawEmissions) + balance := sk.BankKeeper.GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() + require.Equal(t, sdk.ZeroInt().String(), balance) + }) + } From f2d4d84768a74f837eece318f4b2c9fa557c3f81 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:12:24 -0500 Subject: [PATCH 12/20] fixed to error messages --- x/emissions/client/cli/tx.go | 2 +- ..._withdraw_emssions.go => tx_withdraw_emssions.go} | 4 ++-- x/emissions/keeper/msg_server_withdraw_emissions.go | 2 +- x/emissions/types/errors.go | 12 +++++------- x/emissions/types/message_withdraw_emissions.go | 4 ++-- x/emissions/types/message_withdraw_emissons_test.go | 6 ++++++ 6 files changed, 17 insertions(+), 13 deletions(-) rename x/emissions/client/cli/{tx_create_withdraw_emssions.go => tx_withdraw_emssions.go} (90%) diff --git a/x/emissions/client/cli/tx.go b/x/emissions/client/cli/tx.go index 78bcee33b5..888a192327 100644 --- a/x/emissions/client/cli/tx.go +++ b/x/emissions/client/cli/tx.go @@ -18,6 +18,6 @@ func GetTxCmd() *cobra.Command { SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } - cmd.AddCommand(CmdCreateWithdrawEmission()) + cmd.AddCommand(CmdWithdrawEmission()) return cmd } diff --git a/x/emissions/client/cli/tx_create_withdraw_emssions.go b/x/emissions/client/cli/tx_withdraw_emssions.go similarity index 90% rename from x/emissions/client/cli/tx_create_withdraw_emssions.go rename to x/emissions/client/cli/tx_withdraw_emssions.go index a3a60fb406..fff24330dd 100644 --- a/x/emissions/client/cli/tx_create_withdraw_emssions.go +++ b/x/emissions/client/cli/tx_withdraw_emssions.go @@ -11,9 +11,9 @@ import ( "github.com/zeta-chain/zetacore/x/emissions/types" ) -func CmdCreateWithdrawEmission() *cobra.Command { +func CmdWithdrawEmission() *cobra.Command { cmd := &cobra.Command{ - Use: "create-withdraw-emission [amount]", + Use: "withdraw-emission [amount]", Short: "create a new withdrawEmission", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 1fa1287540..213906ccdf 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -32,7 +32,7 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra err = k.RemoveWithdrawableEmission(ctx, msg.Creator, msg.Amount) if err != nil { - return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error()) + return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, fmt.Sprintf("error while removing withdrawable emission for address %s : %s", msg.Creator, err)) } err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, msg.Amount))) if err != nil { diff --git a/x/emissions/types/errors.go b/x/emissions/types/errors.go index e5f778bc25..e741408d81 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -2,12 +2,10 @@ package types import errorsmod "cosmossdk.io/errors" -// DONTCOVER - var ( - ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "Emissions not found") - ErrUnableToWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "Unable to withdraw emissions") - ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "Invalid address") - ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "Rewards pool does not have enough balance") - ErrInvalidAmount = errorsmod.Register(ModuleName, 1005, "Invalid amount") + ErrEmissionsNotFound = errorsmod.Register(ModuleName, 1000, "emissions not found") + ErrUnableToWithdrawEmissions = errorsmod.Register(ModuleName, 1002, "unable to withdraw emissions") + ErrInvalidAddress = errorsmod.Register(ModuleName, 1003, "invalid address") + ErrRewardsPoolDoesNotHaveEnoughBalance = errorsmod.Register(ModuleName, 1004, "rewards pool does not have enough balance") + ErrInvalidAmount = errorsmod.Register(ModuleName, 1005, "invalid amount") ) diff --git a/x/emissions/types/message_withdraw_emissions.go b/x/emissions/types/message_withdraw_emissions.go index 2d6759b792..460960f37b 100644 --- a/x/emissions/types/message_withdraw_emissions.go +++ b/x/emissions/types/message_withdraw_emissions.go @@ -43,8 +43,8 @@ func (msg *MsgWithdrawEmission) ValidateBasic() error { if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } - if !msg.Amount.GT(sdkmath.ZeroInt()) { - return errorsmod.Wrapf(ErrInvalidAmount, "invalid amount (%s)", msg.Amount.String()) + if msg.Amount.IsNil() || !msg.Amount.IsPositive() { + return errorsmod.Wrapf(ErrInvalidAmount, "withdraw amount : (%s)", msg.Amount.String()) } return nil } diff --git a/x/emissions/types/message_withdraw_emissons_test.go b/x/emissions/types/message_withdraw_emissons_test.go index 280999cc3a..fcc7ae7fc5 100644 --- a/x/emissions/types/message_withdraw_emissons_test.go +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -29,6 +29,12 @@ func TestMsgWithdrawEmission_ValidateBasic(t *testing.T) { require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) }) + t.Run("invalid nil amount", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sdkmath.Int{}) + err := msg.ValidateBasic() + require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount) + }) + t.Run("valid withdraw message", func(t *testing.T) { msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(1, 100)) err := msg.ValidateBasic() From 01c9e452afc4d2a5cbb7c4278ce768257b91713c Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:13:48 -0500 Subject: [PATCH 13/20] Update x/emissions/keeper/msg_server_withdraw_emissions.go Co-authored-by: Lucas Bertrand --- x/emissions/keeper/msg_server_withdraw_emissions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 213906ccdf..e494f0d21e 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -23,6 +23,7 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra if err != nil { return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) } + // check if the undistributed rewards pool has enough balance to process this request. // This is just a preliminary check, the actual processing at endblock might still fail if the pool balance gets affected. undistributedRewardsBalance := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) From a17d6782d4cbe1c6223baf9b76432231a6812e4d Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:13:56 -0500 Subject: [PATCH 14/20] Update x/emissions/keeper/msg_server_withdraw_emissions.go Co-authored-by: Lucas Bertrand --- x/emissions/keeper/msg_server_withdraw_emissions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index e494f0d21e..69919f6d0c 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -40,5 +40,6 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to adresss %s for amount %s : err %s", address, msg.Amount, err)) return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error()) } + return &types.MsgWithdrawEmissionResponse{}, nil } From 8f7d96ad95d163feccfd7193c08565dbda3e43d9 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:14:02 -0500 Subject: [PATCH 15/20] Update x/emissions/keeper/msg_server_withdraw_emissions.go Co-authored-by: Lucas Bertrand --- x/emissions/keeper/msg_server_withdraw_emissions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 69919f6d0c..74a6f59239 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -35,6 +35,7 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra if err != nil { return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, fmt.Sprintf("error while removing withdrawable emission for address %s : %s", msg.Creator, err)) } + err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, msg.Amount))) if err != nil { ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to adresss %s for amount %s : err %s", address, msg.Amount, err)) From f29adf275015580bcf348542254f38ec4bf5244f Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:30:34 -0500 Subject: [PATCH 16/20] remove minter for UndistributedObserverRewardsPool --- testutil/keeper/keeper.go | 2 +- .../keeper/msg_server_withdraw_emissions_test.go | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index b9b5eab3c9..82d02c9c4b 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -106,7 +106,7 @@ var moduleAccountPerms = map[string][]string{ crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, emissionstypes.ModuleName: {authtypes.Minter}, - emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter, authtypes.Burner}, + emissionstypes.UndistributedObserverRewardsPool: nil, emissionstypes.UndistributedTssRewardsPool: nil, } diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go index f547524b99..b5b47839ad 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions_test.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -21,7 +21,9 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { msgServer := keeper.NewMsgServerImpl(*k) withdrawableEmission := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + err := sk.BankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + err = sk.BankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) require.NoError(t, err) _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ @@ -40,7 +42,9 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { msgServer := keeper.NewMsgServerImpl(*k) withdrawableEmission := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + err := sk.BankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + err = sk.BankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) require.NoError(t, err) _, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{ @@ -85,9 +89,10 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { withdrawableEmission := sample.WithdrawableEmissions(t) k.SetWithdrawableEmission(ctx, withdrawableEmission) - err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + err := sk.BankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) + require.NoError(t, err) + err = sk.BankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount))) require.NoError(t, err) - address, err := sdk.AccAddressFromBech32(withdrawableEmission.Address) require.NoError(t, err) From 22b423e9dd46247623a72f9d00b78fd91ed34786 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 4 Mar 2024 18:52:59 -0500 Subject: [PATCH 17/20] generate files --- docs/cli/zetacored/zetacored_tx_emissions.md | 2 +- ...ssion.md => zetacored_tx_emissions_withdraw-emission.md} | 6 +++--- x/emissions/keeper/msg_server_withdraw_emissions.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename docs/cli/zetacored/{zetacored_tx_emissions_create-withdraw-emission.md => zetacored_tx_emissions_withdraw-emission.md} (95%) diff --git a/docs/cli/zetacored/zetacored_tx_emissions.md b/docs/cli/zetacored/zetacored_tx_emissions.md index 74996a5e84..95d05cb6dd 100644 --- a/docs/cli/zetacored/zetacored_tx_emissions.md +++ b/docs/cli/zetacored/zetacored_tx_emissions.md @@ -25,5 +25,5 @@ zetacored tx emissions [flags] ### SEE ALSO * [zetacored tx](zetacored_tx.md) - Transactions subcommands -* [zetacored tx emissions create-withdraw-emission](zetacored_tx_emissions_create-withdraw-emission.md) - create a new withdrawEmission +* [zetacored tx emissions withdraw-emission](zetacored_tx_emissions_withdraw-emission.md) - create a new withdrawEmission diff --git a/docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md b/docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md similarity index 95% rename from docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md rename to docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md index 508dec9118..e5c549ae85 100644 --- a/docs/cli/zetacored/zetacored_tx_emissions_create-withdraw-emission.md +++ b/docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md @@ -1,9 +1,9 @@ -# tx emissions create-withdraw-emission +# tx emissions withdraw-emission create a new withdrawEmission ``` -zetacored tx emissions create-withdraw-emission [amount] [flags] +zetacored tx emissions withdraw-emission [amount] [flags] ``` ### Options @@ -21,7 +21,7 @@ zetacored tx emissions create-withdraw-emission [amount] [flags] --gas-adjustment float adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored (default 1) --gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom) --generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name) - -h, --help help for create-withdraw-emission + -h, --help help for withdraw-emission --keyring-backend string Select keyring's backend (os|file|kwallet|pass|test|memory) --keyring-dir string The client Keyring directory; if omitted, the default 'home' directory will be used --ledger Use a connected Ledger device diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index 74a6f59239..a2eabc3d4b 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -23,7 +23,7 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra if err != nil { return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error()) } - + // check if the undistributed rewards pool has enough balance to process this request. // This is just a preliminary check, the actual processing at endblock might still fail if the pool balance gets affected. undistributedRewardsBalance := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom) @@ -35,12 +35,12 @@ func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdra if err != nil { return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, fmt.Sprintf("error while removing withdrawable emission for address %s : %s", msg.Creator, err)) } - + err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, msg.Amount))) if err != nil { ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to adresss %s for amount %s : err %s", address, msg.Amount, err)) return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error()) } - + return &types.MsgWithdrawEmissionResponse{}, nil } From c3173de1fc03359344cec0bc814e0cdad8b12377 Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Tue, 5 Mar 2024 14:05:16 +0100 Subject: [PATCH 18/20] Update x/emissions/types/keys.go --- x/emissions/types/keys.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/emissions/types/keys.go b/x/emissions/types/keys.go index d016997343..9f1fa705fc 100644 --- a/x/emissions/types/keys.go +++ b/x/emissions/types/keys.go @@ -23,7 +23,6 @@ const ( // MemStoreKey defines the in-memory store key MemStoreKey = "mem_emissions" WithdrawableEmissionsKey = "WithdrawableEmissions-value-" - WithdrawEmissionsKey = "WithdrawEmissions-value-" SecsInMonth = 30 * 24 * 60 * 60 BlockRewardsInZeta = "210000000" From 3cc3181c4c5bd628e35f9784424f2cc7a42af2d8 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 5 Mar 2024 14:03:40 -0500 Subject: [PATCH 19/20] change comments --- docs/spec/emissions/messages.md | 9 ++++----- ...tx_withdraw_emssions.go => tx_withdraw_emissions..go} | 0 x/emissions/keeper/msg_server_withdraw_emissions.go | 9 ++++----- x/emissions/keeper/msg_server_withdraw_emissions_test.go | 2 ++ x/emissions/keeper/withdrawable_emissions.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename x/emissions/client/cli/{tx_withdraw_emssions.go => tx_withdraw_emissions..go} (100%) diff --git a/docs/spec/emissions/messages.md b/docs/spec/emissions/messages.md index 08b1e89d1f..c5fc4a4520 100644 --- a/docs/spec/emissions/messages.md +++ b/docs/spec/emissions/messages.md @@ -2,11 +2,10 @@ ## MsgWithdrawEmission -WithdrawEmission create a withdraw emission object , which is then process at endblock -The withdraw emission object is created and stored -using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed. -Creating a withdraw does not guarantee that the emission will be processed -All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not. +WithdrawEmission allows the user to withdraw from their withdrawable emissions. +on a successful withdrawal, the amount is transferred from the undistributed rewards pool to the user's account. +if the amount to be withdrawn is greater than the available withdrawable emission, the max available amount is withdrawn. +if the pool does not have enough balance to process this request, an error is returned. ```proto message MsgWithdrawEmission { diff --git a/x/emissions/client/cli/tx_withdraw_emssions.go b/x/emissions/client/cli/tx_withdraw_emissions..go similarity index 100% rename from x/emissions/client/cli/tx_withdraw_emssions.go rename to x/emissions/client/cli/tx_withdraw_emissions..go diff --git a/x/emissions/keeper/msg_server_withdraw_emissions.go b/x/emissions/keeper/msg_server_withdraw_emissions.go index a2eabc3d4b..3bf40e1428 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -10,11 +10,10 @@ import ( "github.com/zeta-chain/zetacore/x/emissions/types" ) -// WithdrawEmission create a withdraw emission object , which is then process at endblock -// The withdraw emission object is created and stored -// using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed. -// Creating a withdraw does not guarantee that the emission will be processed -// All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not. +// WithdrawEmission allows the user to withdraw from their withdrawable emissions. +// on a successful withdrawal, the amount is transferred from the undistributed rewards pool to the user's account. +// if the amount to be withdrawn is greater than the available withdrawable emission, the max available amount is withdrawn. +// if the pool does not have enough balance to process this request, an error is returned. func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdrawEmission) (*types.MsgWithdrawEmissionResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go index b5b47839ad..9153f60887 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions_test.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -34,6 +34,8 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String() require.Equal(t, withdrawableEmission.Amount.String(), balance) + balance = k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom).Amount.String() + require.Equal(t, sdk.ZeroInt().String(), balance) }) t.Run("unable to withdraw emissions with invalid address", func(t *testing.T) { diff --git a/x/emissions/keeper/withdrawable_emissions.go b/x/emissions/keeper/withdrawable_emissions.go index a8d4327b2e..6e9655915d 100644 --- a/x/emissions/keeper/withdrawable_emissions.go +++ b/x/emissions/keeper/withdrawable_emissions.go @@ -49,7 +49,7 @@ func (k Keeper) AddObserverEmission(ctx sdk.Context, address string, amount sdkm } // RemoveWithdrawableEmission removes the given amount from the withdrawable emission of a given address. -// If the amount is greater than the withdrawable emission, it will remove the entire withdrawable emission. +// If the amount is greater than the available withdrawable emissionsf or that address it will remove the entire amount from the withdrawable emissions. // If the amount is negative or zero, it will return an error. func (k Keeper) RemoveWithdrawableEmission(ctx sdk.Context, address string, amount sdkmath.Int) error { we, found := k.GetWithdrawableEmission(ctx, address) From 1ae19df1deb8fe0ee9e48d226e0eff09f328cbb0 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 5 Mar 2024 14:06:47 -0500 Subject: [PATCH 20/20] add description for test --- x/emissions/keeper/msg_server_withdraw_emissions_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/emissions/keeper/msg_server_withdraw_emissions_test.go b/x/emissions/keeper/msg_server_withdraw_emissions_test.go index 9153f60887..e93c79620a 100644 --- a/x/emissions/keeper/msg_server_withdraw_emissions_test.go +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -82,7 +82,7 @@ func TestMsgServer_WithdrawEmission(t *testing.T) { require.ErrorIs(t, err, types.ErrUnableToWithdrawEmissions) }) - t.Run("unable to withdraw emissions if SendCoinsFromModuleToAccount", func(t *testing.T) { + t.Run("unable to withdraw emissions if SendCoinsFromModuleToAccount fails", func(t *testing.T) { k, ctx, sk, _ := keepertest.EmissionKeeperWithMockOptions(t, keepertest.EmissionMockOptions{ UseBankMock: true, })