github.com/iotexproject/iotex-core@v1.14.1-rc1/action/stake_create.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/core/types"
    15  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    16  	"github.com/pkg/errors"
    17  	"google.golang.org/protobuf/proto"
    18  
    19  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    20  	"github.com/iotexproject/iotex-core/pkg/version"
    21  )
    22  
    23  const (
    24  	// CreateStakePayloadGas represents the CreateStake payload gas per uint
    25  	CreateStakePayloadGas = uint64(100)
    26  	// CreateStakeBaseIntrinsicGas represents the base intrinsic gas for CreateStake
    27  	CreateStakeBaseIntrinsicGas = uint64(10000)
    28  
    29  	_createStakeInterfaceABI = `[
    30  		{
    31  			"inputs": [
    32  				{
    33  					"internalType": "string",
    34  					"name": "candName",
    35  					"type": "string"
    36  				},
    37  				{
    38  					"internalType": "uint256",
    39  					"name": "amount",
    40  					"type": "uint256"
    41  				},
    42  				{
    43  					"internalType": "uint32",
    44  					"name": "duration",
    45  					"type": "uint32"
    46  				},
    47  				{
    48  					"internalType": "bool",
    49  					"name": "autoStake",
    50  					"type": "bool"
    51  				},
    52  				{
    53  					"internalType": "uint8[]",
    54  					"name": "data",
    55  					"type": "uint8[]"
    56  				}
    57  			],
    58  			"name": "createStake",
    59  			"outputs": [],
    60  			"stateMutability": "nonpayable",
    61  			"type": "function"
    62  		}
    63  	]`
    64  )
    65  
    66  var (
    67  	// _createStakeMethod is the interface of the abi encoding of stake action
    68  	_createStakeMethod abi.Method
    69  	_                  EthCompatibleAction = (*CreateStake)(nil)
    70  
    71  	errDecodeFailure = errors.New("failed to decode the data")
    72  )
    73  
    74  // CreateStake defines the action of CreateStake creation
    75  type CreateStake struct {
    76  	AbstractAction
    77  
    78  	candName  string
    79  	amount    *big.Int
    80  	duration  uint32
    81  	autoStake bool
    82  	payload   []byte
    83  }
    84  
    85  func init() {
    86  	createStakeInterface, err := abi.JSON(strings.NewReader(_createStakeInterfaceABI))
    87  	if err != nil {
    88  		panic(err)
    89  	}
    90  	var ok bool
    91  	_createStakeMethod, ok = createStakeInterface.Methods["createStake"]
    92  	if !ok {
    93  		panic("fail to load the method")
    94  	}
    95  }
    96  
    97  // NewCreateStake returns a CreateStake instance
    98  func NewCreateStake(
    99  	nonce uint64,
   100  	candidateName, amount string,
   101  	duration uint32,
   102  	autoStake bool,
   103  	payload []byte,
   104  	gasLimit uint64,
   105  	gasPrice *big.Int,
   106  ) (*CreateStake, error) {
   107  	stake, ok := new(big.Int).SetString(amount, 10)
   108  	if !ok {
   109  		return nil, errors.Wrapf(ErrInvalidAmount, "amount %s", amount)
   110  	}
   111  
   112  	return &CreateStake{
   113  		AbstractAction: AbstractAction{
   114  			version:  version.ProtocolVersion,
   115  			nonce:    nonce,
   116  			gasLimit: gasLimit,
   117  			gasPrice: gasPrice,
   118  		},
   119  		candName:  candidateName,
   120  		amount:    stake,
   121  		duration:  duration,
   122  		autoStake: autoStake,
   123  		payload:   payload,
   124  	}, nil
   125  }
   126  
   127  // Amount returns the amount
   128  func (cs *CreateStake) Amount() *big.Int { return cs.amount }
   129  
   130  // Payload returns the payload bytes
   131  func (cs *CreateStake) Payload() []byte { return cs.payload }
   132  
   133  // Candidate returns the candidate name
   134  func (cs *CreateStake) Candidate() string { return cs.candName }
   135  
   136  // Duration returns the CreateStaked duration
   137  func (cs *CreateStake) Duration() uint32 { return cs.duration }
   138  
   139  // AutoStake returns the flag of AutoStake s
   140  func (cs *CreateStake) AutoStake() bool { return cs.autoStake }
   141  
   142  // Serialize returns a raw byte stream of the CreateStake struct
   143  func (cs *CreateStake) Serialize() []byte {
   144  	return byteutil.Must(proto.Marshal(cs.Proto()))
   145  }
   146  
   147  // Proto converts to protobuf CreateStake Action
   148  func (cs *CreateStake) Proto() *iotextypes.StakeCreate {
   149  	act := iotextypes.StakeCreate{
   150  		CandidateName:  cs.candName,
   151  		StakedDuration: cs.duration,
   152  		AutoStake:      cs.autoStake,
   153  	}
   154  
   155  	if cs.amount != nil {
   156  		act.StakedAmount = cs.amount.String()
   157  	}
   158  
   159  	if len(cs.payload) > 0 {
   160  		act.Payload = make([]byte, len(cs.payload))
   161  		copy(act.Payload, cs.payload)
   162  	}
   163  	return &act
   164  }
   165  
   166  // LoadProto converts a protobuf's Action to CreateStake
   167  func (cs *CreateStake) LoadProto(pbAct *iotextypes.StakeCreate) error {
   168  	if pbAct == nil {
   169  		return ErrNilProto
   170  	}
   171  
   172  	cs.candName = pbAct.GetCandidateName()
   173  	cs.duration = pbAct.StakedDuration
   174  	cs.autoStake = pbAct.AutoStake
   175  
   176  	if len(pbAct.GetStakedAmount()) > 0 {
   177  		var ok bool
   178  		if cs.amount, ok = new(big.Int).SetString(pbAct.StakedAmount, 10); !ok {
   179  			return errors.Errorf("invalid amount %s", pbAct.StakedAmount)
   180  		}
   181  	}
   182  
   183  	cs.payload = nil
   184  	if len(pbAct.Payload) > 0 {
   185  		cs.payload = make([]byte, len(pbAct.Payload))
   186  		copy(cs.payload, pbAct.Payload)
   187  	}
   188  	return nil
   189  }
   190  
   191  // IntrinsicGas returns the intrinsic gas of a CreateStake
   192  func (cs *CreateStake) IntrinsicGas() (uint64, error) {
   193  	payloadSize := uint64(len(cs.Payload()))
   194  	return CalculateIntrinsicGas(CreateStakeBaseIntrinsicGas, CreateStakePayloadGas, payloadSize)
   195  }
   196  
   197  // Cost returns the total cost of a CreateStake
   198  func (cs *CreateStake) Cost() (*big.Int, error) {
   199  	intrinsicGas, err := cs.IntrinsicGas()
   200  	if err != nil {
   201  		return nil, errors.Wrap(err, "failed to get intrinsic gas for the CreateStake creates")
   202  	}
   203  	CreateStakeFee := big.NewInt(0).Mul(cs.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
   204  	return big.NewInt(0).Add(cs.Amount(), CreateStakeFee), nil
   205  }
   206  
   207  // SanityCheck validates the variables in the action
   208  func (cs *CreateStake) SanityCheck() error {
   209  	if cs.Amount().Sign() <= 0 {
   210  		return errors.Wrap(ErrInvalidAmount, "negative value")
   211  	}
   212  	if !IsValidCandidateName(cs.candName) {
   213  		return ErrInvalidCanName
   214  	}
   215  	return cs.AbstractAction.SanityCheck()
   216  }
   217  
   218  // EncodeABIBinary encodes data in abi encoding
   219  func (cs *CreateStake) EncodeABIBinary() ([]byte, error) {
   220  	return cs.encodeABIBinary()
   221  }
   222  
   223  func (cs *CreateStake) encodeABIBinary() ([]byte, error) {
   224  	data, err := _createStakeMethod.Inputs.Pack(cs.candName, cs.amount, cs.duration, cs.autoStake, cs.payload)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	return append(_createStakeMethod.ID, data...), nil
   229  }
   230  
   231  // NewCreateStakeFromABIBinary decodes data into createStake action
   232  func NewCreateStakeFromABIBinary(data []byte) (*CreateStake, error) {
   233  	var (
   234  		paramsMap = map[string]interface{}{}
   235  		ok        bool
   236  		cs        CreateStake
   237  	)
   238  	// sanity check
   239  	if len(data) <= 4 || !bytes.Equal(_createStakeMethod.ID, data[:4]) {
   240  		return nil, errDecodeFailure
   241  	}
   242  	if err := _createStakeMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil {
   243  		return nil, err
   244  	}
   245  	if cs.candName, ok = paramsMap["candName"].(string); !ok {
   246  		return nil, errDecodeFailure
   247  	}
   248  	if cs.amount, ok = paramsMap["amount"].(*big.Int); !ok {
   249  		return nil, errDecodeFailure
   250  	}
   251  	if cs.duration, ok = paramsMap["duration"].(uint32); !ok {
   252  		return nil, errDecodeFailure
   253  	}
   254  	if cs.autoStake, ok = paramsMap["autoStake"].(bool); !ok {
   255  		return nil, errDecodeFailure
   256  	}
   257  	if cs.payload, ok = paramsMap["data"].([]byte); !ok {
   258  		return nil, errDecodeFailure
   259  	}
   260  	return &cs, nil
   261  }
   262  
   263  // ToEthTx converts action to eth-compatible tx
   264  func (cs *CreateStake) ToEthTx(_ uint32) (*types.Transaction, error) {
   265  	data, err := cs.encodeABIBinary()
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	return types.NewTx(&types.LegacyTx{
   270  		Nonce:    cs.Nonce(),
   271  		GasPrice: cs.GasPrice(),
   272  		Gas:      cs.GasLimit(),
   273  		To:       &_stakingProtocolEthAddr,
   274  		Value:    big.NewInt(0),
   275  		Data:     data,
   276  	}), nil
   277  }