Skip to content

Commit 589e228

Browse files
authored
feat: message withdraw emissions (#1825)
1 parent 422e00e commit 589e228

22 files changed

+1192
-22
lines changed

changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
### Features
4646

4747
* [1698](https://github.com/zeta-chain/node/issues/1698) - bitcoin dynamic depositor fee
48+
* [1811](https://github.com/zeta-chain/node/pull/1811) - add a message to withdraw emission rewards
4849

4950
### Docs
5051

docs/cli/zetacored/zetacored_tx_emissions.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ zetacored tx emissions [flags]
2525
### SEE ALSO
2626

2727
* [zetacored tx](zetacored_tx.md) - Transactions subcommands
28+
* [zetacored tx emissions withdraw-emission](zetacored_tx_emissions_withdraw-emission.md) - create a new withdrawEmission
2829

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# tx emissions withdraw-emission
2+
3+
create a new withdrawEmission
4+
5+
```
6+
zetacored tx emissions withdraw-emission [amount] [flags]
7+
```
8+
9+
### Options
10+
11+
```
12+
-a, --account-number uint The account number of the signing account (offline mode only)
13+
--aux Generate aux signer data instead of sending a tx
14+
-b, --broadcast-mode string Transaction broadcasting mode (sync|async|block)
15+
--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)
16+
--fee-granter string Fee granter grants fees for the transaction
17+
--fee-payer string Fee payer pays fees for the transaction instead of deducting from the signer
18+
--fees string Fees to pay along with transaction; eg: 10uatom
19+
--from string Name or address of private key with which to sign
20+
--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)
21+
--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)
22+
--gas-prices string Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)
23+
--generate-only Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase only accessed when providing a key name)
24+
-h, --help help for withdraw-emission
25+
--keyring-backend string Select keyring's backend (os|file|kwallet|pass|test|memory)
26+
--keyring-dir string The client Keyring directory; if omitted, the default 'home' directory will be used
27+
--ledger Use a connected Ledger device
28+
--node string [host]:[port] to tendermint rpc interface for this chain
29+
--note string Note to add a description to the transaction (previously --memo)
30+
--offline Offline mode (does not allow any online functionality)
31+
-o, --output string Output format (text|json)
32+
-s, --sequence uint The sequence number of the signing account (offline mode only)
33+
--sign-mode string Choose sign mode (direct|amino-json|direct-aux), this is an advanced feature
34+
--timeout-height uint Set a block timeout height to prevent the tx from being committed past a certain height
35+
--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
36+
-y, --yes Skip tx broadcasting prompt confirmation
37+
```
38+
39+
### Options inherited from parent commands
40+
41+
```
42+
--chain-id string The network chain ID
43+
--home string directory for config and data
44+
--log_format string The logging format (json|plain)
45+
--log_level string The logging level (trace|debug|info|warn|error|fatal|panic)
46+
--trace print out full stack trace on errors
47+
```
48+
49+
### SEE ALSO
50+
51+
* [zetacored tx emissions](zetacored_tx_emissions.md) - emissions transactions subcommands
52+

docs/openapi/openapi.swagger.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -54011,6 +54011,8 @@ definitions:
5401154011
type: string
5401254012
proved:
5401354013
type: boolean
54014+
emissionsMsgWithdrawEmissionResponse:
54015+
type: object
5401454016
emissionsQueryGetEmissionsFactorsResponse:
5401554017
type: object
5401654018
properties:

docs/spec/emissions/messages.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Messages
2+
3+
## MsgWithdrawEmission
4+
5+
WithdrawEmission allows the user to withdraw from their withdrawable emissions.
6+
on a successful withdrawal, the amount is transferred from the undistributed rewards pool to the user's account.
7+
if the amount to be withdrawn is greater than the available withdrawable emission, the max available amount is withdrawn.
8+
if the pool does not have enough balance to process this request, an error is returned.
9+
10+
```proto
11+
message MsgWithdrawEmission {
12+
string creator = 1;
13+
string amount = 2;
14+
}
15+
```
16+

proto/emissions/tx.proto

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,16 @@ import "gogoproto/gogo.proto";
66
option go_package = "github.com/zeta-chain/zetacore/x/emissions/types";
77

88
// Msg defines the Msg service.
9-
service Msg {}
9+
service Msg {
10+
rpc WithdrawEmission(MsgWithdrawEmission) returns (MsgWithdrawEmissionResponse);
11+
}
12+
13+
message MsgWithdrawEmission {
14+
string creator = 1;
15+
string amount = 2 [
16+
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
17+
(gogoproto.nullable) = false
18+
];
19+
}
20+
21+
message MsgWithdrawEmissionResponse {}

testutil/keeper/emissions.go

+6
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,9 @@ func EmissionKeeperWithMockOptions(
106106

107107
return k, ctx, sdkKeepers, zetaKeepers
108108
}
109+
110+
func GetEmissionsBankMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.EmissionBankKeeper {
111+
cbk, ok := keeper.GetBankKeeper().(*emissionsmocks.EmissionBankKeeper)
112+
require.True(t, ok)
113+
return cbk
114+
}

testutil/keeper/mocks/emissions/bank.go

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testutil/keeper/mocks/emissions/emission.go

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/emissions/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export * from "./events_pb";
22
export * from "./genesis_pb";
33
export * from "./params_pb";
44
export * from "./query_pb";
5+
export * from "./tx_pb";
56
export * from "./withdrawable_emissions_pb";

typescript/emissions/tx_pb.d.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @generated by protoc-gen-es v1.3.0 with parameter "target=dts"
2+
// @generated from file emissions/tx.proto (package zetachain.zetacore.emissions, syntax proto3)
3+
/* eslint-disable */
4+
// @ts-nocheck
5+
6+
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
7+
import { Message, proto3 } from "@bufbuild/protobuf";
8+
9+
/**
10+
* @generated from message zetachain.zetacore.emissions.MsgWithdrawEmission
11+
*/
12+
export declare class MsgWithdrawEmission extends Message<MsgWithdrawEmission> {
13+
/**
14+
* @generated from field: string creator = 1;
15+
*/
16+
creator: string;
17+
18+
/**
19+
* @generated from field: string amount = 2;
20+
*/
21+
amount: string;
22+
23+
constructor(data?: PartialMessage<MsgWithdrawEmission>);
24+
25+
static readonly runtime: typeof proto3;
26+
static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmission";
27+
static readonly fields: FieldList;
28+
29+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): MsgWithdrawEmission;
30+
31+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): MsgWithdrawEmission;
32+
33+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): MsgWithdrawEmission;
34+
35+
static equals(a: MsgWithdrawEmission | PlainMessage<MsgWithdrawEmission> | undefined, b: MsgWithdrawEmission | PlainMessage<MsgWithdrawEmission> | undefined): boolean;
36+
}
37+
38+
/**
39+
* @generated from message zetachain.zetacore.emissions.MsgWithdrawEmissionResponse
40+
*/
41+
export declare class MsgWithdrawEmissionResponse extends Message<MsgWithdrawEmissionResponse> {
42+
constructor(data?: PartialMessage<MsgWithdrawEmissionResponse>);
43+
44+
static readonly runtime: typeof proto3;
45+
static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmissionResponse";
46+
static readonly fields: FieldList;
47+
48+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): MsgWithdrawEmissionResponse;
49+
50+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): MsgWithdrawEmissionResponse;
51+
52+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): MsgWithdrawEmissionResponse;
53+
54+
static equals(a: MsgWithdrawEmissionResponse | PlainMessage<MsgWithdrawEmissionResponse> | undefined, b: MsgWithdrawEmissionResponse | PlainMessage<MsgWithdrawEmissionResponse> | undefined): boolean;
55+
}
56+

x/emissions/client/cli/tx.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ func GetTxCmd() *cobra.Command {
1818
SuggestionsMinimumDistance: 2,
1919
RunE: client.ValidateCmd,
2020
}
21-
21+
cmd.AddCommand(CmdWithdrawEmission())
2222
return cmd
2323
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cli
2+
3+
import (
4+
"errors"
5+
6+
sdkmath "cosmossdk.io/math"
7+
"github.com/cosmos/cosmos-sdk/client"
8+
"github.com/cosmos/cosmos-sdk/client/flags"
9+
"github.com/cosmos/cosmos-sdk/client/tx"
10+
"github.com/spf13/cobra"
11+
"github.com/zeta-chain/zetacore/x/emissions/types"
12+
)
13+
14+
func CmdWithdrawEmission() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "withdraw-emission [amount]",
17+
Short: "create a new withdrawEmission",
18+
Args: cobra.ExactArgs(1),
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
argsAmount, ok := sdkmath.NewIntFromString(args[0])
21+
if !ok {
22+
return errors.New("invalid amount")
23+
}
24+
clientCtx, err := client.GetClientTxContext(cmd)
25+
if err != nil {
26+
return err
27+
}
28+
msg := types.NewMsgWithdrawEmissions(clientCtx.GetFromAddress().String(), argsAmount)
29+
if err := msg.ValidateBasic(); err != nil {
30+
return err
31+
}
32+
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
33+
},
34+
}
35+
flags.AddTxFlagsToCmd(cmd)
36+
return cmd
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package keeper
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
errorsmod "cosmossdk.io/errors"
8+
sdk "github.com/cosmos/cosmos-sdk/types"
9+
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
10+
"github.com/zeta-chain/zetacore/x/emissions/types"
11+
)
12+
13+
// WithdrawEmission allows the user to withdraw from their withdrawable emissions.
14+
// on a successful withdrawal, the amount is transferred from the undistributed rewards pool to the user's account.
15+
// if the amount to be withdrawn is greater than the available withdrawable emission, the max available amount is withdrawn.
16+
// if the pool does not have enough balance to process this request, an error is returned.
17+
func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdrawEmission) (*types.MsgWithdrawEmissionResponse, error) {
18+
ctx := sdk.UnwrapSDKContext(goCtx)
19+
20+
// check if the creator address is valid
21+
address, err := sdk.AccAddressFromBech32(msg.Creator)
22+
if err != nil {
23+
return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error())
24+
}
25+
26+
// check if the undistributed rewards pool has enough balance to process this request.
27+
// This is just a preliminary check, the actual processing at endblock might still fail if the pool balance gets affected.
28+
undistributedRewardsBalance := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom)
29+
if undistributedRewardsBalance.Amount.LT(msg.Amount) {
30+
return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request")
31+
}
32+
33+
err = k.RemoveWithdrawableEmission(ctx, msg.Creator, msg.Amount)
34+
if err != nil {
35+
return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, fmt.Sprintf("error while removing withdrawable emission for address %s : %s", msg.Creator, err))
36+
}
37+
38+
err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.UndistributedObserverRewardsPool, address, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, msg.Amount)))
39+
if err != nil {
40+
ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to adresss %s for amount %s : err %s", address, msg.Amount, err))
41+
return nil, errorsmod.Wrap(types.ErrUnableToWithdrawEmissions, err.Error())
42+
}
43+
44+
return &types.MsgWithdrawEmissionResponse{}, nil
45+
}

0 commit comments

Comments
 (0)