github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/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  	"math/big"
    11  
    12  	"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
    13  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  	"google.golang.org/grpc/codes"
    17  	"google.golang.org/grpc/status"
    18  
    19  	"github.com/iotexproject/iotex-core/ioctl"
    20  	"github.com/iotexproject/iotex-core/ioctl/config"
    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|DELEGATE_ADDRESS]",
    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  // NewNodeRewardCmd represents the node reward command
    41  func NewNodeRewardCmd(client ioctl.Client) *cobra.Command {
    42  	use, _ := client.SelectTranslation(_rewardCmdUses)
    43  	short, _ := client.SelectTranslation(_rewardCmdShorts)
    44  	long, _ := client.SelectTranslation(_rewardPoolLong)
    45  
    46  	return &cobra.Command{
    47  		Use:   use,
    48  		Short: short,
    49  		Args:  cobra.RangeArgs(1, 2),
    50  		Long:  long,
    51  		RunE: func(cmd *cobra.Command, args []string) error {
    52  			cmd.SilenceUsage = true
    53  			switch args[0] {
    54  			case "pool":
    55  				if len(args) != 1 {
    56  					return errors.New("wrong number of arg(s) for ioctl node reward pool command. \nRun 'ioctl node reward --help' for usage")
    57  				}
    58  				totalUnclaimed, totalAvailable, totalBalance, err := rewardPool(client)
    59  				if err != nil {
    60  					return err
    61  				}
    62  				cmd.Printf("Total Unclaimed:\t %s IOTX\nTotal Available:\t %s IOTX\nTotal Balance:\t\t %s IOTX\n",
    63  					totalUnclaimed, totalAvailable, totalBalance)
    64  			case "unclaimed":
    65  				if len(args) != 2 {
    66  					return errors.New("wrong number of arg(s) for ioctl node reward unclaimed [ALIAS|DELEGATE_ADDRESS] command. \nRun 'ioctl node reward --help' for usage")
    67  				}
    68  				address, reward, err := reward(client, args[1])
    69  				if err != nil {
    70  					return err
    71  				}
    72  				cmd.Printf("%s: %s IOTX\n", address, reward)
    73  			default:
    74  				return errors.New("unknown command. \nRun 'ioctl node reward --help' for usage")
    75  			}
    76  			return nil
    77  		},
    78  	}
    79  }
    80  
    81  func rewardPool(client ioctl.Client) (string, string, string, error) {
    82  	apiClient, err := client.APIServiceClient()
    83  	if err != nil {
    84  		return "", "", "", err
    85  	}
    86  	ctx := context.Background()
    87  	jwtMD, err := util.JwtAuth()
    88  	if err == nil {
    89  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
    90  	}
    91  	response, err := apiClient.ReadState(
    92  		ctx,
    93  		&iotexapi.ReadStateRequest{
    94  			ProtocolID: []byte("rewarding"),
    95  			MethodName: []byte("AvailableBalance"),
    96  		},
    97  	)
    98  	if err != nil {
    99  		if sta, ok := status.FromError(err); ok {
   100  			if sta.Code() == codes.Unavailable {
   101  				return "", "", "", ioctl.ErrInvalidEndpointOrInsecure
   102  			}
   103  			return "", "", "", errors.New(sta.Message())
   104  		}
   105  		return "", "", "", errors.Wrap(err, "failed to invoke ReadState api")
   106  	}
   107  
   108  	availableRewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   109  	if !ok {
   110  		return "", "", "", errors.New("failed to convert string into big int")
   111  	}
   112  
   113  	response, err = apiClient.ReadState(
   114  		context.Background(),
   115  		&iotexapi.ReadStateRequest{
   116  			ProtocolID: []byte("rewarding"),
   117  			MethodName: []byte("TotalBalance"),
   118  		},
   119  	)
   120  	if err != nil {
   121  		if sta, ok := status.FromError(err); ok {
   122  			if sta.Code() == codes.Unavailable {
   123  				return "", "", "", ioctl.ErrInvalidEndpointOrInsecure
   124  			}
   125  			return "", "", "", errors.New(sta.Message())
   126  		}
   127  		return "", "", "", errors.Wrap(err, "failed to invoke ReadState api")
   128  	}
   129  	totalRewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   130  	if !ok {
   131  		return "", "", "", errors.New("failed to convert string into big int")
   132  	}
   133  
   134  	totalUnclaimedRewardRau := big.NewInt(0)
   135  	totalUnclaimedRewardRau.Sub(totalRewardRau, availableRewardRau)
   136  
   137  	return util.RauToString(totalUnclaimedRewardRau, util.IotxDecimalNum),
   138  		util.RauToString(availableRewardRau, util.IotxDecimalNum),
   139  		util.RauToString(totalRewardRau, util.IotxDecimalNum),
   140  		err
   141  }
   142  
   143  func reward(client ioctl.Client, arg string) (string, string, error) {
   144  	address, err := client.Address(arg)
   145  	if err != nil {
   146  		return "", "", errors.Wrap(err, "failed to get address")
   147  	}
   148  	apiClient, err := client.APIServiceClient()
   149  	if err != nil {
   150  		return "", "", errors.Wrap(err, "failed to connect to endpoint")
   151  	}
   152  	ctx := context.Background()
   153  	jwtMD, err := util.JwtAuth()
   154  	if err == nil {
   155  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   156  	}
   157  	response, err := apiClient.ReadState(
   158  		ctx,
   159  		&iotexapi.ReadStateRequest{
   160  			ProtocolID: []byte("rewarding"),
   161  			MethodName: []byte("UnclaimedBalance"),
   162  			Arguments:  [][]byte{[]byte(address)},
   163  		},
   164  	)
   165  	if err != nil {
   166  		if sta, ok := status.FromError(err); ok {
   167  			if sta.Code() == codes.Unavailable {
   168  				return "", "", ioctl.ErrInvalidEndpointOrInsecure
   169  			}
   170  			return "", "", errors.New(sta.Message())
   171  		}
   172  		return "", "", errors.Wrap(err, "failed to get version from server")
   173  	}
   174  	rewardRau, ok := new(big.Int).SetString(string(response.Data), 10)
   175  	if !ok {
   176  		return "", "", errors.New("failed to convert string into big int")
   177  	}
   178  	return address, util.RauToString(rewardRau, util.IotxDecimalNum), err
   179  }