github.com/iotexproject/iotex-core@v1.14.1-rc1/action/candidate_register.go (about)

     1  // Copyright (c) 2020 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 action
     7  
     8  import (
     9  	"bytes"
    10  	"math/big"
    11  	"strings"
    12  
    13  	"github.com/ethereum/go-ethereum/accounts/abi"
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/core/types"
    16  	"github.com/iotexproject/iotex-address/address"
    17  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    18  	"github.com/pkg/errors"
    19  	"google.golang.org/protobuf/proto"
    20  
    21  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    22  	"github.com/iotexproject/iotex-core/pkg/version"
    23  )
    24  
    25  const (
    26  	// CandidateRegisterPayloadGas represents the CandidateRegister payload gas per uint
    27  	CandidateRegisterPayloadGas = uint64(100)
    28  	// CandidateRegisterBaseIntrinsicGas represents the base intrinsic gas for CandidateRegister
    29  	CandidateRegisterBaseIntrinsicGas = uint64(10000)
    30  
    31  	_candidateRegisterInterfaceABI = `[
    32  		{
    33  			"inputs": [
    34  				{
    35  					"internalType": "string",
    36  					"name": "name",
    37  					"type": "string"
    38  				},
    39  				{
    40  					"internalType": "address",
    41  					"name": "operatorAddress",
    42  					"type": "address"
    43  				},
    44  				{
    45  					"internalType": "address",
    46  					"name": "rewardAddress",
    47  					"type": "address"
    48  				},
    49  				{
    50  					"internalType": "address",
    51  					"name": "ownerAddress",
    52  					"type": "address"
    53  				},
    54  				{
    55  					"internalType": "uint256",
    56  					"name": "amount",
    57  					"type": "uint256"
    58  				},
    59  				{
    60  					"internalType": "uint32",
    61  					"name": "duration",
    62  					"type": "uint32"
    63  				},
    64  				{
    65  					"internalType": "bool",
    66  					"name": "autoStake",
    67  					"type": "bool"
    68  				},
    69  				{
    70  					"internalType": "uint8[]",
    71  					"name": "data",
    72  					"type": "uint8[]"
    73  				}
    74  			],
    75  			"name": "candidateRegister",
    76  			"outputs": [],
    77  			"stateMutability": "nonpayable",
    78  			"type": "function"
    79  		}
    80  	]`
    81  )
    82  
    83  var (
    84  	// _candidateRegisterInterface is the interface of the abi encoding of stake action
    85  	_candidateRegisterMethod abi.Method
    86  
    87  	// ErrInvalidAmount represents that amount is 0 or negative
    88  	ErrInvalidAmount = errors.New("invalid amount")
    89  
    90  	//ErrInvalidCanName represents that candidate name is invalid
    91  	ErrInvalidCanName = errors.New("invalid candidate name")
    92  
    93  	_ EthCompatibleAction = (*CandidateRegister)(nil)
    94  )
    95  
    96  // CandidateRegister is the action to register a candidate
    97  type CandidateRegister struct {
    98  	AbstractAction
    99  
   100  	name            string
   101  	operatorAddress address.Address
   102  	rewardAddress   address.Address
   103  	ownerAddress    address.Address
   104  	amount          *big.Int
   105  	duration        uint32
   106  	autoStake       bool
   107  	payload         []byte
   108  }
   109  
   110  func init() {
   111  	candidateRegisterInterface, err := abi.JSON(strings.NewReader(_candidateRegisterInterfaceABI))
   112  	if err != nil {
   113  		panic(err)
   114  	}
   115  	var ok bool
   116  	_candidateRegisterMethod, ok = candidateRegisterInterface.Methods["candidateRegister"]
   117  	if !ok {
   118  		panic("fail to load the method")
   119  	}
   120  }
   121  
   122  // NewCandidateRegister creates a CandidateRegister instance
   123  func NewCandidateRegister(
   124  	nonce uint64,
   125  	name, operatorAddrStr, rewardAddrStr, ownerAddrStr, amountStr string,
   126  	duration uint32,
   127  	autoStake bool,
   128  	payload []byte,
   129  	gasLimit uint64,
   130  	gasPrice *big.Int,
   131  ) (*CandidateRegister, error) {
   132  	operatorAddr, err := address.FromString(operatorAddrStr)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	rewardAddress, err := address.FromString(rewardAddrStr)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	amount, ok := new(big.Int).SetString(amountStr, 10)
   143  	if !ok {
   144  		return nil, errors.Wrapf(ErrInvalidAmount, "amount %s", amount)
   145  	}
   146  
   147  	cr := &CandidateRegister{
   148  		AbstractAction: AbstractAction{
   149  			version:  version.ProtocolVersion,
   150  			nonce:    nonce,
   151  			gasLimit: gasLimit,
   152  			gasPrice: gasPrice,
   153  		},
   154  		name:            name,
   155  		operatorAddress: operatorAddr,
   156  		rewardAddress:   rewardAddress,
   157  		amount:          amount,
   158  		duration:        duration,
   159  		autoStake:       autoStake,
   160  		payload:         payload,
   161  	}
   162  
   163  	if len(ownerAddrStr) > 0 {
   164  		ownerAddress, err := address.FromString(ownerAddrStr)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		cr.ownerAddress = ownerAddress
   169  	}
   170  	return cr, nil
   171  }
   172  
   173  // Amount returns the amount
   174  func (cr *CandidateRegister) Amount() *big.Int { return cr.amount }
   175  
   176  // Payload returns the payload bytes
   177  func (cr *CandidateRegister) Payload() []byte { return cr.payload }
   178  
   179  // Duration returns the self-stake duration
   180  func (cr *CandidateRegister) Duration() uint32 { return cr.duration }
   181  
   182  // AutoStake returns the if staking is auth stake
   183  func (cr *CandidateRegister) AutoStake() bool { return cr.autoStake }
   184  
   185  // Name returns candidate name to register
   186  func (cr *CandidateRegister) Name() string { return cr.name }
   187  
   188  // OperatorAddress returns candidate operatorAddress to register
   189  func (cr *CandidateRegister) OperatorAddress() address.Address { return cr.operatorAddress }
   190  
   191  // RewardAddress returns candidate rewardAddress to register
   192  func (cr *CandidateRegister) RewardAddress() address.Address { return cr.rewardAddress }
   193  
   194  // OwnerAddress returns candidate ownerAddress to register
   195  func (cr *CandidateRegister) OwnerAddress() address.Address { return cr.ownerAddress }
   196  
   197  // Serialize returns a raw byte stream of the CandidateRegister struct
   198  func (cr *CandidateRegister) Serialize() []byte {
   199  	return byteutil.Must(proto.Marshal(cr.Proto()))
   200  }
   201  
   202  // Proto converts to protobuf CandidateRegister Action
   203  func (cr *CandidateRegister) Proto() *iotextypes.CandidateRegister {
   204  	act := iotextypes.CandidateRegister{
   205  		Candidate: &iotextypes.CandidateBasicInfo{
   206  			Name:            cr.name,
   207  			OperatorAddress: cr.operatorAddress.String(),
   208  			RewardAddress:   cr.rewardAddress.String(),
   209  		},
   210  		StakedDuration: cr.duration,
   211  		AutoStake:      cr.autoStake,
   212  	}
   213  
   214  	if cr.amount != nil {
   215  		act.StakedAmount = cr.amount.String()
   216  	}
   217  
   218  	if cr.ownerAddress != nil {
   219  		act.OwnerAddress = cr.ownerAddress.String()
   220  	}
   221  
   222  	if len(cr.payload) > 0 {
   223  		act.Payload = make([]byte, len(cr.payload))
   224  		copy(act.Payload, cr.payload)
   225  	}
   226  	return &act
   227  }
   228  
   229  // LoadProto converts a protobuf's Action to CandidateRegister
   230  func (cr *CandidateRegister) LoadProto(pbAct *iotextypes.CandidateRegister) error {
   231  	if pbAct == nil {
   232  		return ErrNilProto
   233  	}
   234  
   235  	cInfo := pbAct.GetCandidate()
   236  	cr.name = cInfo.GetName()
   237  
   238  	operatorAddr, err := address.FromString(cInfo.GetOperatorAddress())
   239  	if err != nil {
   240  		return err
   241  	}
   242  	rewardAddr, err := address.FromString(cInfo.GetRewardAddress())
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	cr.operatorAddress = operatorAddr
   248  	cr.rewardAddress = rewardAddr
   249  	cr.duration = pbAct.GetStakedDuration()
   250  	cr.autoStake = pbAct.GetAutoStake()
   251  
   252  	if len(pbAct.GetStakedAmount()) > 0 {
   253  		var ok bool
   254  		if cr.amount, ok = new(big.Int).SetString(pbAct.GetStakedAmount(), 10); !ok {
   255  			return errors.Errorf("invalid amount %s", pbAct.GetStakedAmount())
   256  		}
   257  	}
   258  
   259  	cr.payload = nil
   260  	if len(pbAct.GetPayload()) > 0 {
   261  		cr.payload = make([]byte, len(pbAct.GetPayload()))
   262  		copy(cr.payload, pbAct.GetPayload())
   263  	}
   264  
   265  	if len(pbAct.GetOwnerAddress()) > 0 {
   266  		ownerAddr, err := address.FromString(pbAct.GetOwnerAddress())
   267  		if err != nil {
   268  			return err
   269  		}
   270  		cr.ownerAddress = ownerAddr
   271  	}
   272  	return nil
   273  }
   274  
   275  // IntrinsicGas returns the intrinsic gas of a CandidateRegister
   276  func (cr *CandidateRegister) IntrinsicGas() (uint64, error) {
   277  	payloadSize := uint64(len(cr.Payload()))
   278  	return CalculateIntrinsicGas(CandidateRegisterBaseIntrinsicGas, CandidateRegisterPayloadGas, payloadSize)
   279  }
   280  
   281  // Cost returns the total cost of a CandidateRegister
   282  func (cr *CandidateRegister) Cost() (*big.Int, error) {
   283  	intrinsicGas, err := cr.IntrinsicGas()
   284  	if err != nil {
   285  		return nil, errors.Wrap(err, "failed to get intrinsic gas for the CandidateRegister creates")
   286  	}
   287  	fee := big.NewInt(0).Mul(cr.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
   288  	return big.NewInt(0).Add(cr.Amount(), fee), nil
   289  }
   290  
   291  // SanityCheck validates the variables in the action
   292  func (cr *CandidateRegister) SanityCheck() error {
   293  	if cr.Amount().Sign() < 0 {
   294  		return errors.Wrap(ErrInvalidAmount, "negative value")
   295  	}
   296  	if !IsValidCandidateName(cr.Name()) {
   297  		return ErrInvalidCanName
   298  	}
   299  
   300  	return cr.AbstractAction.SanityCheck()
   301  }
   302  
   303  // EncodeABIBinary encodes data in abi encoding
   304  func (cr *CandidateRegister) EncodeABIBinary() ([]byte, error) {
   305  	return cr.encodeABIBinary()
   306  }
   307  
   308  func (cr *CandidateRegister) encodeABIBinary() ([]byte, error) {
   309  	if cr.operatorAddress == nil {
   310  		return nil, ErrAddress
   311  	}
   312  	if cr.rewardAddress == nil {
   313  		return nil, ErrAddress
   314  	}
   315  	if cr.ownerAddress == nil {
   316  		return nil, ErrAddress
   317  	}
   318  	data, err := _candidateRegisterMethod.Inputs.Pack(
   319  		cr.name,
   320  		common.BytesToAddress(cr.operatorAddress.Bytes()),
   321  		common.BytesToAddress(cr.rewardAddress.Bytes()),
   322  		common.BytesToAddress(cr.ownerAddress.Bytes()),
   323  		cr.amount,
   324  		cr.duration,
   325  		cr.autoStake,
   326  		cr.payload)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  	return append(_candidateRegisterMethod.ID, data...), nil
   331  }
   332  
   333  // NewCandidateRegisterFromABIBinary decodes data into CandidateRegister action
   334  func NewCandidateRegisterFromABIBinary(data []byte) (*CandidateRegister, error) {
   335  	var (
   336  		paramsMap = map[string]interface{}{}
   337  		ok        bool
   338  		err       error
   339  		cr        CandidateRegister
   340  	)
   341  	// sanity check
   342  	if len(data) <= 4 || !bytes.Equal(_candidateRegisterMethod.ID, data[:4]) {
   343  		return nil, errDecodeFailure
   344  	}
   345  	if err := _candidateRegisterMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil {
   346  		return nil, err
   347  	}
   348  	if cr.name, ok = paramsMap["name"].(string); !ok {
   349  		return nil, errDecodeFailure
   350  	}
   351  	if cr.operatorAddress, err = ethAddrToNativeAddr(paramsMap["operatorAddress"]); err != nil {
   352  		return nil, err
   353  	}
   354  	if cr.rewardAddress, err = ethAddrToNativeAddr(paramsMap["rewardAddress"]); err != nil {
   355  		return nil, err
   356  	}
   357  	if cr.ownerAddress, err = ethAddrToNativeAddr(paramsMap["ownerAddress"]); err != nil {
   358  		return nil, err
   359  	}
   360  	if cr.amount, ok = paramsMap["amount"].(*big.Int); !ok {
   361  		return nil, errDecodeFailure
   362  	}
   363  	if cr.duration, ok = paramsMap["duration"].(uint32); !ok {
   364  		return nil, errDecodeFailure
   365  	}
   366  	if cr.autoStake, ok = paramsMap["autoStake"].(bool); !ok {
   367  		return nil, errDecodeFailure
   368  	}
   369  	if cr.payload, ok = paramsMap["data"].([]byte); !ok {
   370  		return nil, errDecodeFailure
   371  	}
   372  	return &cr, nil
   373  }
   374  
   375  func ethAddrToNativeAddr(in interface{}) (address.Address, error) {
   376  	ethAddr, ok := in.(common.Address)
   377  	if !ok {
   378  		return nil, errDecodeFailure
   379  	}
   380  	return address.FromBytes(ethAddr.Bytes())
   381  }
   382  
   383  // ToEthTx converts action to eth-compatible tx
   384  func (cr *CandidateRegister) ToEthTx(_ uint32) (*types.Transaction, error) {
   385  	data, err := cr.encodeABIBinary()
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  	return types.NewTx(&types.LegacyTx{
   390  		Nonce:    cr.Nonce(),
   391  		GasPrice: cr.GasPrice(),
   392  		Gas:      cr.GasLimit(),
   393  		To:       &_stakingProtocolEthAddr,
   394  		Value:    big.NewInt(0),
   395  		Data:     data,
   396  	}), nil
   397  }
   398  
   399  // IsValidCandidateName check if a candidate name string is valid.
   400  func IsValidCandidateName(s string) bool {
   401  	if len(s) == 0 || len(s) > 12 {
   402  		return false
   403  	}
   404  	for _, c := range s {
   405  		if !(('a' <= c && c <= 'z') || ('0' <= c && c <= '9')) {
   406  			return false
   407  		}
   408  	}
   409  	return true
   410  }