From 589e2289e91866f578c683b65d5c554e27a6768d Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 5 Mar 2024 14:29:31 -0500 Subject: [PATCH] feat: message withdraw emissions (#1825) --- changelog.md | 1 + docs/cli/zetacored/zetacored_tx_emissions.md | 1 + ...etacored_tx_emissions_withdraw-emission.md | 52 ++ docs/openapi/openapi.swagger.yaml | 2 + docs/spec/emissions/messages.md | 16 + proto/emissions/tx.proto | 14 +- testutil/keeper/emissions.go | 6 + testutil/keeper/mocks/emissions/bank.go | 18 + testutil/keeper/mocks/emissions/emission.go | 24 + typescript/emissions/index.d.ts | 1 + typescript/emissions/tx_pb.d.ts | 56 ++ x/emissions/client/cli/tx.go | 2 +- .../client/cli/tx_withdraw_emissions..go | 37 ++ .../keeper/msg_server_withdraw_emissions.go | 45 ++ .../msg_server_withdraw_emissions_test.go | 117 ++++ x/emissions/keeper/withdrawable_emissions.go | 22 + .../keeper/withdrawable_emissions_test.go | 178 ++++++ x/emissions/types/errors.go | 15 +- x/emissions/types/expected_keepers.go | 1 + .../types/message_withdraw_emissions.go | 50 ++ .../types/message_withdraw_emissons_test.go | 43 ++ x/emissions/types/tx.pb.go | 513 +++++++++++++++++- 22 files changed, 1192 insertions(+), 22 deletions(-) create mode 100644 docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md 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/client/cli/tx_withdraw_emissions..go create mode 100644 x/emissions/keeper/msg_server_withdraw_emissions.go create mode 100644 x/emissions/keeper/msg_server_withdraw_emissions_test.go create mode 100644 x/emissions/keeper/withdrawable_emissions_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/changelog.md b/changelog.md index 2c8a745b32..9da339d95a 100644 --- a/changelog.md +++ b/changelog.md @@ -45,6 +45,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/docs/cli/zetacored/zetacored_tx_emissions.md b/docs/cli/zetacored/zetacored_tx_emissions.md index f782c072d1..95d05cb6dd 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 withdraw-emission](zetacored_tx_emissions_withdraw-emission.md) - create a new withdrawEmission diff --git a/docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md b/docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md new file mode 100644 index 0000000000..e5c549ae85 --- /dev/null +++ b/docs/cli/zetacored/zetacored_tx_emissions_withdraw-emission.md @@ -0,0 +1,52 @@ +# tx emissions withdraw-emission + +create a new withdrawEmission + +``` +zetacored tx emissions 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 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/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 3ee890602d..8e2cb02f21 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -54011,6 +54011,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..c5fc4a4520 --- /dev/null +++ b/docs/spec/emissions/messages.md @@ -0,0 +1,16 @@ +# Messages + +## MsgWithdrawEmission + +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 { + string creator = 1; + string amount = 2; +} +``` + 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/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index 31479d77b7..a359d93249 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -106,3 +106,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/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/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/x/emissions/client/cli/tx.go b/x/emissions/client/cli/tx.go index 8e1fe8632e..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(CmdWithdrawEmission()) return cmd } diff --git a/x/emissions/client/cli/tx_withdraw_emissions..go b/x/emissions/client/cli/tx_withdraw_emissions..go new file mode 100644 index 0000000000..fff24330dd --- /dev/null +++ b/x/emissions/client/cli/tx_withdraw_emissions..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 CmdWithdrawEmission() *cobra.Command { + cmd := &cobra.Command{ + Use: "withdraw-emission [amount]", + 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..3bf40e1428 --- /dev/null +++ b/x/emissions/keeper/msg_server_withdraw_emissions.go @@ -0,0 +1,45 @@ +package keeper + +import ( + "context" + "fmt" + + 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 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) + + // check if the creator address is valid + address, err := sdk.AccAddressFromBech32(msg.Creator) + 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) + if undistributedRewardsBalance.Amount.LT(msg.Amount) { + return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request") + } + + err = k.RemoveWithdrawableEmission(ctx, msg.Creator, msg.Amount) + 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 +} 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..e93c79620a --- /dev/null +++ b/x/emissions/keeper/msg_server_withdraw_emissions_test.go @@ -0,0 +1,117 @@ +package keeper_test + +import ( + "testing" + + 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" + "github.com/zeta-chain/zetacore/testutil/sample" + "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", 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.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{ + Creator: withdrawableEmission.Address, + Amount: withdrawableEmission.Amount, + }) + require.NoError(t, err) + + 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) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + + msgServer := keeper.NewMsgServerImpl(*k) + withdrawableEmission := sample.WithdrawableEmissions(t) + k.SetWithdrawableEmission(ctx, withdrawableEmission) + 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{ + Creator: "invalid_address", + Amount: withdrawableEmission.Amount, + }) + require.ErrorIs(t, err, types.ErrInvalidAddress) + }) + + 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) + 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 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.ErrUnableToWithdrawEmissions) + }) + + t.Run("unable to withdraw emissions if SendCoinsFromModuleToAccount fails", 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.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) + + 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) + }) + +} diff --git a/x/emissions/keeper/withdrawable_emissions.go b/x/emissions/keeper/withdrawable_emissions.go index cdb478eda8..6e9655915d 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,7 +48,27 @@ func (k Keeper) AddObserverEmission(ctx sdk.Context, address string, amount sdkm k.SetWithdrawableEmission(ctx, we) } +// RemoveWithdrawableEmission removes the given amount from the withdrawable emission of a given address. +// 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) + if !found { + return types.ErrEmissionsNotFound + } + if amount.IsNegative() || amount.IsZero() { + return types.ErrInvalidAmount + } + if amount.GT(we.Amount) { + amount = we.Amount + } + 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. +// 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 new file mode 100644 index 0000000000..f81cb1517f --- /dev/null +++ b/x/emissions/keeper/withdrawable_emissions_test.go @@ -0,0 +1,178 @@ +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("slash observer emission", 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) + }) + +} +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) + 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) + }) +} + +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) + 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("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) + 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) + }) +} + +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/types/errors.go b/x/emissions/types/errors.go index 856baedd89..e741408d81 100644 --- a/x/emissions/types/errors.go +++ b/x/emissions/types/errors.go @@ -1,14 +1,11 @@ package types -// DONTCOVER +import errorsmod "cosmossdk.io/errors" -import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -// 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") + 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/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/message_withdraw_emissions.go b/x/emissions/types/message_withdraw_emissions.go new file mode 100644 index 0000000000..460960f37b --- /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.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 new file mode 100644 index 0000000000..fcc7ae7fc5 --- /dev/null +++ b/x/emissions/types/message_withdraw_emissons_test.go @@ -0,0 +1,43 @@ +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" + 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("invalid negative amount", func(t *testing.T) { + msg := emissionstypes.NewMsgWithdrawEmissions(sample.AccAddress(), sample.IntInRange(-100, -1)) + err := msg.ValidateBasic() + 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("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() + require.NoError(t, err) + }) +} 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") +)