github.com/iotexproject/iotex-core@v1.14.1-rc1/action/stake_reclaim.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/pkg/errors"
    16  	"google.golang.org/protobuf/proto"
    17  
    18  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    19  
    20  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    21  	"github.com/iotexproject/iotex-core/pkg/version"
    22  )
    23  
    24  const (
    25  	// ReclaimStakePayloadGas represents the stake reclaim payload gas per uint
    26  	ReclaimStakePayloadGas = uint64(100)
    27  	// ReclaimStakeBaseIntrinsicGas represents the base intrinsic gas for stake reclaim
    28  	ReclaimStakeBaseIntrinsicGas = uint64(10000)
    29  
    30  	_reclaimStakeInterfaceABI = `[
    31  		{
    32  			"inputs": [
    33  				{
    34  					"internalType": "uint64",
    35  					"name": "bucketIndex",
    36  					"type": "uint64"
    37  				},
    38  				{
    39  					"internalType": "uint8[]",
    40  					"name": "data",
    41  					"type": "uint8[]"
    42  				}
    43  			],
    44  			"name": "unstake",
    45  			"outputs": [],
    46  			"stateMutability": "nonpayable",
    47  			"type": "function"
    48  		},
    49  		{
    50  			"inputs": [
    51  				{
    52  					"internalType": "uint64",
    53  					"name": "bucketIndex",
    54  					"type": "uint64"
    55  				},
    56  				{
    57  					"internalType": "uint8[]",
    58  					"name": "data",
    59  					"type": "uint8[]"
    60  				}
    61  			],
    62  			"name": "withdrawStake",
    63  			"outputs": [],
    64  			"stateMutability": "nonpayable",
    65  			"type": "function"
    66  		}
    67  	]`
    68  )
    69  
    70  var (
    71  	// _unstakeMethod is the interface of the abi encoding of unstake action
    72  	_unstakeMethod abi.Method
    73  	// _withdrawStakeMethod is the interface of the abi encoding of withdrawStake action
    74  	_withdrawStakeMethod abi.Method
    75  	_                    EthCompatibleAction = (*Unstake)(nil)
    76  	_                    EthCompatibleAction = (*WithdrawStake)(nil)
    77  )
    78  
    79  func init() {
    80  	reclaimStakeInterface, err := abi.JSON(strings.NewReader(_reclaimStakeInterfaceABI))
    81  	if err != nil {
    82  		panic(err)
    83  	}
    84  	var ok bool
    85  	_unstakeMethod, ok = reclaimStakeInterface.Methods["unstake"]
    86  	if !ok {
    87  		panic("fail to load the method")
    88  	}
    89  	_withdrawStakeMethod, ok = reclaimStakeInterface.Methods["withdrawStake"]
    90  	if !ok {
    91  		panic("fail to load the method")
    92  	}
    93  }
    94  
    95  // reclaimStake defines the action of stake restake/withdraw
    96  type reclaimStake struct {
    97  	AbstractAction
    98  
    99  	bucketIndex uint64
   100  	payload     []byte
   101  }
   102  
   103  // BucketIndex returns bucket index
   104  func (sr *reclaimStake) BucketIndex() uint64 { return sr.bucketIndex }
   105  
   106  // Payload returns the payload bytes
   107  func (sr *reclaimStake) Payload() []byte { return sr.payload }
   108  
   109  // Serialize returns a raw byte stream of the stake reclaim action struct
   110  func (sr *reclaimStake) Serialize() []byte {
   111  	return byteutil.Must(proto.Marshal(sr.Proto()))
   112  }
   113  
   114  // Proto converts to protobuf stake reclaim action struct
   115  func (sr *reclaimStake) Proto() *iotextypes.StakeReclaim {
   116  	act := &iotextypes.StakeReclaim{
   117  		BucketIndex: sr.bucketIndex,
   118  		Payload:     sr.payload,
   119  	}
   120  
   121  	return act
   122  }
   123  
   124  // LoadProto converts a protobuf's Action to reclaimStake
   125  func (sr *reclaimStake) LoadProto(pbAct *iotextypes.StakeReclaim) error {
   126  	if pbAct == nil {
   127  		return ErrNilProto
   128  	}
   129  
   130  	sr.bucketIndex = pbAct.GetBucketIndex()
   131  	sr.payload = pbAct.GetPayload()
   132  	return nil
   133  }
   134  
   135  // Unstake defines the action of unstake
   136  type Unstake struct {
   137  	reclaimStake
   138  }
   139  
   140  // NewUnstake returns a Unstake instance
   141  func NewUnstake(
   142  	nonce uint64,
   143  	bucketIndex uint64,
   144  	payload []byte,
   145  	gasLimit uint64,
   146  	gasPrice *big.Int,
   147  ) (*Unstake, error) {
   148  	return &Unstake{
   149  		reclaimStake{
   150  			AbstractAction: AbstractAction{
   151  				version:  version.ProtocolVersion,
   152  				nonce:    nonce,
   153  				gasLimit: gasLimit,
   154  				gasPrice: gasPrice,
   155  			},
   156  			bucketIndex: bucketIndex,
   157  			payload:     payload,
   158  		},
   159  	}, nil
   160  }
   161  
   162  // IntrinsicGas returns the intrinsic gas of a Unstake
   163  func (su *Unstake) IntrinsicGas() (uint64, error) {
   164  	payloadSize := uint64(len(su.Payload()))
   165  	return CalculateIntrinsicGas(ReclaimStakeBaseIntrinsicGas, ReclaimStakePayloadGas, payloadSize)
   166  }
   167  
   168  // Cost returns the total cost of a Unstake
   169  func (su *Unstake) Cost() (*big.Int, error) {
   170  	intrinsicGas, err := su.IntrinsicGas()
   171  	if err != nil {
   172  		return nil, errors.Wrap(err, "failed to get intrinsic gas for the unstake")
   173  	}
   174  	unstakeFee := big.NewInt(0).Mul(su.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
   175  	return unstakeFee, nil
   176  }
   177  
   178  // EncodeABIBinary encodes data in abi encoding
   179  func (su *Unstake) EncodeABIBinary() ([]byte, error) {
   180  	return su.encodeABIBinary()
   181  }
   182  
   183  func (su *Unstake) encodeABIBinary() ([]byte, error) {
   184  	data, err := _unstakeMethod.Inputs.Pack(su.bucketIndex, su.payload)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	return append(_unstakeMethod.ID, data...), nil
   189  }
   190  
   191  // NewUnstakeFromABIBinary decodes data into WithdrawStake action
   192  func NewUnstakeFromABIBinary(data []byte) (*Unstake, error) {
   193  	var (
   194  		paramsMap = map[string]interface{}{}
   195  		ok        bool
   196  		su        Unstake
   197  	)
   198  	// sanity check
   199  	if len(data) <= 4 || !bytes.Equal(_unstakeMethod.ID, data[:4]) {
   200  		return nil, errDecodeFailure
   201  	}
   202  	if err := _unstakeMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil {
   203  		return nil, err
   204  	}
   205  	if su.bucketIndex, ok = paramsMap["bucketIndex"].(uint64); !ok {
   206  		return nil, errDecodeFailure
   207  	}
   208  	if su.payload, ok = paramsMap["data"].([]byte); !ok {
   209  		return nil, errDecodeFailure
   210  	}
   211  	return &su, nil
   212  }
   213  
   214  // ToEthTx converts action to eth-compatible tx
   215  func (su *Unstake) ToEthTx(_ uint32) (*types.Transaction, error) {
   216  	data, err := su.encodeABIBinary()
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	return types.NewTx(&types.LegacyTx{
   221  		Nonce:    su.Nonce(),
   222  		GasPrice: su.GasPrice(),
   223  		Gas:      su.GasLimit(),
   224  		To:       &_stakingProtocolEthAddr,
   225  		Value:    big.NewInt(0),
   226  		Data:     data,
   227  	}), nil
   228  }
   229  
   230  // WithdrawStake defines the action of stake withdraw
   231  type WithdrawStake struct {
   232  	reclaimStake
   233  }
   234  
   235  // NewWithdrawStake returns a WithdrawStake instance
   236  func NewWithdrawStake(
   237  	nonce uint64,
   238  	bucketIndex uint64,
   239  	payload []byte,
   240  	gasLimit uint64,
   241  	gasPrice *big.Int,
   242  ) (*WithdrawStake, error) {
   243  	return &WithdrawStake{
   244  		reclaimStake{
   245  			AbstractAction: AbstractAction{
   246  				version:  version.ProtocolVersion,
   247  				nonce:    nonce,
   248  				gasLimit: gasLimit,
   249  				gasPrice: gasPrice,
   250  			},
   251  			bucketIndex: bucketIndex,
   252  			payload:     payload,
   253  		},
   254  	}, nil
   255  }
   256  
   257  // IntrinsicGas returns the intrinsic gas of a WithdrawStake
   258  func (sw *WithdrawStake) IntrinsicGas() (uint64, error) {
   259  	payloadSize := uint64(len(sw.Payload()))
   260  	return CalculateIntrinsicGas(ReclaimStakeBaseIntrinsicGas, ReclaimStakePayloadGas, payloadSize)
   261  }
   262  
   263  // Cost returns the total cost of a WithdrawStake
   264  func (sw *WithdrawStake) Cost() (*big.Int, error) {
   265  	intrinsicGas, err := sw.IntrinsicGas()
   266  	if err != nil {
   267  		return nil, errors.Wrap(err, "failed to get intrinsic gas for the WithdrawStake")
   268  	}
   269  	withdrawFee := big.NewInt(0).Mul(sw.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
   270  	return withdrawFee, nil
   271  }
   272  
   273  // EncodeABIBinary encodes data in abi encoding
   274  func (sw *WithdrawStake) EncodeABIBinary() ([]byte, error) {
   275  	return sw.encodeABIBinary()
   276  }
   277  
   278  func (sw *WithdrawStake) encodeABIBinary() ([]byte, error) {
   279  	data, err := _withdrawStakeMethod.Inputs.Pack(sw.bucketIndex, sw.payload)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	return append(_withdrawStakeMethod.ID, data...), nil
   284  }
   285  
   286  // NewWithdrawStakeFromABIBinary decodes data into WithdrawStake action
   287  func NewWithdrawStakeFromABIBinary(data []byte) (*WithdrawStake, error) {
   288  	var (
   289  		paramsMap = map[string]interface{}{}
   290  		ok        bool
   291  		sw        WithdrawStake
   292  	)
   293  	// sanity check
   294  	if len(data) <= 4 || !bytes.Equal(_withdrawStakeMethod.ID, data[:4]) {
   295  		return nil, errDecodeFailure
   296  	}
   297  	if err := _withdrawStakeMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil {
   298  		return nil, err
   299  	}
   300  	if sw.bucketIndex, ok = paramsMap["bucketIndex"].(uint64); !ok {
   301  		return nil, errDecodeFailure
   302  	}
   303  	if sw.payload, ok = paramsMap["data"].([]byte); !ok {
   304  		return nil, errDecodeFailure
   305  	}
   306  	return &sw, nil
   307  }
   308  
   309  // ToEthTx converts action to eth-compatible tx
   310  func (sw *WithdrawStake) ToEthTx(_ uint32) (*types.Transaction, error) {
   311  	data, err := sw.encodeABIBinary()
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	return types.NewTx(&types.LegacyTx{
   316  		Nonce:    sw.Nonce(),
   317  		GasPrice: sw.GasPrice(),
   318  		Gas:      sw.GasLimit(),
   319  		To:       &_stakingProtocolEthAddr,
   320  		Value:    big.NewInt(0),
   321  		Data:     data,
   322  	}), nil
   323  }