github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/node/nodereward.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package node
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"math/big"
    12  
    13  	"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
    14  	"github.com/spf13/cobra"
    15  	"google.golang.org/grpc/status"
    16  
    17  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    18  
    19  	"github.com/iotexproject/iotex-core/ioctl/config"
    20  	"github.com/iotexproject/iotex-core/ioctl/output"
    21  	"github.com/iotexproject/iotex-core/ioctl/util"
    22  )
    23  
    24  // Multi-language support
    25  var (
    26  	_rewardCmdUses = map[config.Language]string{
    27  		config.English: "reward unclaimed|pool [ALIAS|REWARD_ADDRESS|NAME]",
    28  		config.Chinese: "reward 未支取|奖金池 [别名|奖励地址|名称]",
    29  	}
    30  	_rewardCmdShorts = map[config.Language]string{
    31  		config.English: "Query rewards",
    32  		config.Chinese: "查询奖励",
    33  	}
    34  	_rewardPoolLong = map[config.Language]string{
    35  		config.English: "ioctl node reward pool returns unclaimed and available Rewards in fund pool.\nTotalUnclaimed is the amount of all delegates that have been issued but are not claimed;\nTotalAvailable is the amount of balance that has not been issued to anyone.\n\nioctl node reward unclaimed [ALIAS|DELEGATE_ADDRESS] returns unclaimed rewards of a specific delegate.",
    36  		config.Chinese: "ioctl node reward 返回奖金池中的未支取奖励和可获取的奖励. TotalUnclaimed是所有代表已被发放但未支取的奖励的总和; TotalAvailable 是奖金池中未被发放的奖励的总和.\n\nioctl node [ALIAS|DELEGATE_ADDRESS] 返回特定代表的已被发放但未支取的奖励.",
    37  	}
    38  )
    39  
    40  // _nodeRewardCmd represents the node reward command
    41  var _nodeRewardCmd = &cobra.Command{
    42  	Use:   config.TranslateInLang(_rewardCmdUses, config.UILanguage),
    43  	Short: config.TranslateInLang(_rewardCmdShorts, config.UILanguage),
    44  	Args:  cobra.RangeArgs(1, 2),
    45  	Long:  config.TranslateInLang(_rewardPoolLong, config.UILanguage),
    46  	RunE: func(cmd *cobra.Command, args []string) error {
    47  		cmd.SilenceUsage = true
    48  		var err error
    49  		switch args[0] {
    50  		case "pool":
    51  			if len(args) != 1 {
    52  				return output.NewError(output.InputError, "wrong number of arg(s) for ioctl node reward pool command. \nRun 'ioctl node reward --help' for usage.", nil)
    53  			}
    54  			err = rewardPool()
    55  		case "unclaimed":
    56  			if len(args) != 2 {
    57  				return output.NewError(output.InputError, "wrong number of arg(s) for ioctl node reward unclaimed [ALIAS|DELEGATE_ADDRESS] command. \nRun 'ioctl node reward --help' for usage.", nil)
    58  			}
    59  			err = reward(args[1])
    60  		default:
    61  			return output.NewError(output.InputError, "unknown command. \nRun 'ioctl node reward --help' for usage.", nil)
    62  		}
    63  		return output.PrintError(err)
    64  	},
    65  }
    66  
    67  // TotalBalance == Total rewards in the pool
    68  // TotalAvailable == Rewards in the pool that has not been issued to anyone
    69  // TotalUnclaimed == Rewards in the pool that has been issued to a delegate but are not claimed yet
    70  type rewardPoolMessage struct {
    71  	TotalBalance   string `json:"TotalBalance"`
    72  	TotalUnclaimed string `json:"TotalUnclaimed"`
    73  	TotalAvailable string `json:"TotalAvailable"`
    74  }
    75  
    76  func (m *rewardPoolMessage) String() string {
    77  	if output.Format == "" {
    78  		message := fmt.Sprintf("Total Unclaimed:\t %s IOTX\nTotal Available:\t %s IOTX\nTotal Balance:\t\t %s IOTX",
    79  			m.TotalUnclaimed, m.TotalAvailable, m.TotalBalance)
    80  		return message
    81  	}
    82  	return output.FormatString(output.Result, m)
    83  }
    84  
    85  type rewardMessage struct {
    86  	Address string `json:"address"`
    87  	Reward  string `json:"reward"`
    88  }
    89  
    90  func (m *rewardMessage) String() string {
    91  	if output.Format == "" {
    92  		message := fmt.Sprintf("%s: %s IOTX", m.Address, m.Reward)
    93  		return message
    94  	}
    95  	return output.FormatString(output.Result, m)
    96  }
    97  
    98  func rewardPool() error {
    99  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
   100  	if err != nil {
   101  		return output.NewError(output.NetworkError, "failed to connect to endpoint", err)
   102  	}
   103  	defer conn.Close()
   104  	cli := iotexapi.NewAPIServiceClient(conn)
   105  	ctx := context.Background()
   106  
   107  	jwtMD, err := util.JwtAuth()
   108  	if err == nil {
   109  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   110  	}
   111  	// AvailableBalance == Rewards in the pool that has not been issued to anyone
   112  	request := &iotexapi.ReadStateRequest{
   113  		ProtocolID: []byte("rewarding"),
   114  		MethodName: []byte("AvailableBalance"),
   115  	}
   116  	response, err := cli.ReadState(ctx, request)
   117  	if err != nil {
   118  		sta, ok := status.FromError(err)
   119  		if ok {
   120  			return output.NewError(output.APIError, sta.Message(), nil)
   121  		}
   122  		return output.NewError(output.NetworkError, "failed to invoke ReadState api", err)
   123  	}
   124  	availableRewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   125  	if !ok {
   126  		return output.NewError(output.ConvertError, "failed to convert string into big int", err)
   127  	}
   128  	// TotalBalance == Total rewards in the pool
   129  	request = &iotexapi.ReadStateRequest{
   130  		ProtocolID: []byte("rewarding"),
   131  		MethodName: []byte("TotalBalance"),
   132  	}
   133  	response, err = cli.ReadState(ctx, request)
   134  	if err != nil {
   135  		sta, ok := status.FromError(err)
   136  		if ok {
   137  			return output.NewError(output.APIError, sta.Message(), nil)
   138  		}
   139  		return output.NewError(output.NetworkError, "failed to invoke ReadState api", err)
   140  	}
   141  	totalRewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   142  	if !ok {
   143  		return output.NewError(output.ConvertError, "failed to convert string into big int", err)
   144  	}
   145  	// TotalUnclaimedBalance == Rewards in the pool that has been issued and unclaimed
   146  	totalUnclaimedRewardRau := big.NewInt(0)
   147  	totalUnclaimedRewardRau.Sub(totalRewardRau, availableRewardRau)
   148  	message := rewardPoolMessage{
   149  		TotalBalance:   util.RauToString(totalRewardRau, util.IotxDecimalNum),
   150  		TotalUnclaimed: util.RauToString(totalUnclaimedRewardRau, util.IotxDecimalNum),
   151  		TotalAvailable: util.RauToString(availableRewardRau, util.IotxDecimalNum),
   152  	}
   153  	fmt.Println(message.String())
   154  	return nil
   155  }
   156  
   157  func reward(arg string) error {
   158  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
   159  	if err != nil {
   160  		return output.NewError(output.NetworkError, "failed to connect to endpoint", err)
   161  	}
   162  	defer conn.Close()
   163  	cli := iotexapi.NewAPIServiceClient(conn)
   164  
   165  	address, err := getCandidateRewardAddressByAddressOrName(cli, arg)
   166  	if err != nil {
   167  		return output.NewError(output.AddressError, "failed to get address", err)
   168  	}
   169  
   170  	ctx := context.Background()
   171  
   172  	jwtMD, err := util.JwtAuth()
   173  	if err == nil {
   174  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   175  	}
   176  
   177  	request := &iotexapi.ReadStateRequest{
   178  		ProtocolID: []byte("rewarding"),
   179  		MethodName: []byte("UnclaimedBalance"),
   180  		Arguments:  [][]byte{[]byte(address)},
   181  	}
   182  	response, err := cli.ReadState(ctx, request)
   183  	if err != nil {
   184  		sta, ok := status.FromError(err)
   185  		if ok {
   186  			return output.NewError(output.APIError, sta.Message(), nil)
   187  		}
   188  		return output.NewError(output.NetworkError, "failed to invoke ReadState api", err)
   189  	}
   190  	rewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   191  	if !ok {
   192  		return output.NewError(output.ConvertError, "failed to convert string into big int", err)
   193  	}
   194  	message := rewardMessage{Address: address, Reward: util.RauToString(rewardRau, util.IotxDecimalNum)}
   195  	fmt.Println(message.String())
   196  	return nil
   197  }