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

     1  // Copyright (c) 2019 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  	"math/big"
    10  
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/iotexproject/iotex-address/address"
    14  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    15  	"github.com/pkg/errors"
    16  	"google.golang.org/protobuf/proto"
    17  
    18  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    19  	"github.com/iotexproject/iotex-core/pkg/version"
    20  )
    21  
    22  const (
    23  	// TransferPayloadGas represents the transfer payload gas per uint
    24  	TransferPayloadGas = uint64(100)
    25  	// TransferBaseIntrinsicGas represents the base intrinsic gas for transfer
    26  	TransferBaseIntrinsicGas = uint64(10000)
    27  )
    28  
    29  var (
    30  	_ hasDestination      = (*Transfer)(nil)
    31  	_ EthCompatibleAction = (*Transfer)(nil)
    32  )
    33  
    34  // Transfer defines the struct of account-based transfer
    35  type Transfer struct {
    36  	AbstractAction
    37  
    38  	amount    *big.Int
    39  	recipient string
    40  	payload   []byte
    41  }
    42  
    43  // NewTransfer returns a Transfer instance
    44  func NewTransfer(
    45  	nonce uint64,
    46  	amount *big.Int,
    47  	recipient string,
    48  	payload []byte,
    49  	gasLimit uint64,
    50  	gasPrice *big.Int,
    51  ) (*Transfer, error) {
    52  	return &Transfer{
    53  		AbstractAction: AbstractAction{
    54  			version:  version.ProtocolVersion,
    55  			nonce:    nonce,
    56  			gasLimit: gasLimit,
    57  			gasPrice: gasPrice,
    58  		},
    59  		recipient: recipient,
    60  		amount:    amount,
    61  		payload:   payload,
    62  		// SenderPublicKey and Signature will be populated in Sign()
    63  	}, nil
    64  }
    65  
    66  // Amount returns the amount
    67  func (tsf *Transfer) Amount() *big.Int { return tsf.amount }
    68  
    69  // Payload returns the payload bytes
    70  func (tsf *Transfer) Payload() []byte { return tsf.payload }
    71  
    72  // Recipient returns the recipient address. It's the wrapper of Action.DstAddr
    73  func (tsf *Transfer) Recipient() string { return tsf.recipient }
    74  
    75  // Destination returns the recipient address as destination.
    76  func (tsf *Transfer) Destination() string { return tsf.recipient }
    77  
    78  // TotalSize returns the total size of this Transfer
    79  func (tsf *Transfer) TotalSize() uint32 {
    80  	size := tsf.BasicActionSize()
    81  	if tsf.amount != nil && len(tsf.amount.Bytes()) > 0 {
    82  		size += uint32(len(tsf.amount.Bytes()))
    83  	}
    84  	// 65 is the pubkey size
    85  	return size + uint32(len(tsf.payload)) + 65
    86  }
    87  
    88  // Serialize returns a raw byte stream of this Transfer
    89  func (tsf *Transfer) Serialize() []byte {
    90  	return byteutil.Must(proto.Marshal(tsf.Proto()))
    91  }
    92  
    93  // Proto converts Transfer to protobuf's Action
    94  func (tsf *Transfer) Proto() *iotextypes.Transfer {
    95  	// used by account-based model
    96  	act := &iotextypes.Transfer{
    97  		Recipient: tsf.recipient,
    98  		Payload:   tsf.payload,
    99  	}
   100  
   101  	if tsf.amount != nil {
   102  		act.Amount = tsf.amount.String()
   103  	}
   104  	return act
   105  }
   106  
   107  // LoadProto converts a protobuf's Action to Transfer
   108  func (tsf *Transfer) LoadProto(pbAct *iotextypes.Transfer) error {
   109  	if pbAct == nil {
   110  		return ErrNilProto
   111  	}
   112  	if tsf == nil {
   113  		return ErrNilAction
   114  	}
   115  	*tsf = Transfer{}
   116  
   117  	tsf.recipient = pbAct.GetRecipient()
   118  	tsf.payload = pbAct.GetPayload()
   119  	if pbAct.GetAmount() == "" {
   120  		tsf.amount = big.NewInt(0)
   121  	} else {
   122  		amount, ok := new(big.Int).SetString(pbAct.GetAmount(), 10)
   123  		// tsf amount gets zero when pbAct.GetAmount is empty string
   124  		if !ok {
   125  			return errors.Errorf("invalid amount %s", pbAct.GetAmount())
   126  		}
   127  		tsf.amount = amount
   128  	}
   129  	return nil
   130  }
   131  
   132  // IntrinsicGas returns the intrinsic gas of a transfer
   133  func (tsf *Transfer) IntrinsicGas() (uint64, error) {
   134  	payloadSize := uint64(len(tsf.Payload()))
   135  	return CalculateIntrinsicGas(TransferBaseIntrinsicGas, TransferPayloadGas, payloadSize)
   136  }
   137  
   138  // Cost returns the total cost of a transfer
   139  func (tsf *Transfer) Cost() (*big.Int, error) {
   140  	intrinsicGas, err := tsf.IntrinsicGas()
   141  	if err != nil {
   142  		return nil, errors.Wrap(err, "failed to get intrinsic gas for the transfer")
   143  	}
   144  	transferFee := big.NewInt(0).Mul(tsf.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
   145  	return big.NewInt(0).Add(tsf.Amount(), transferFee), nil
   146  }
   147  
   148  // SanityCheck validates the variables in the action
   149  func (tsf *Transfer) SanityCheck() error {
   150  	// Reject transfer of negative amount
   151  	if tsf.Amount().Sign() < 0 {
   152  		return ErrNegativeValue
   153  	}
   154  	return tsf.AbstractAction.SanityCheck()
   155  }
   156  
   157  // ToEthTx converts action to eth-compatible tx
   158  func (tsf *Transfer) ToEthTx(_ uint32) (*types.Transaction, error) {
   159  	addr, err := address.FromString(tsf.recipient)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	ethAddr := common.BytesToAddress(addr.Bytes())
   164  	return types.NewTx(&types.LegacyTx{
   165  		Nonce:    tsf.Nonce(),
   166  		GasPrice: tsf.GasPrice(),
   167  		Gas:      tsf.GasLimit(),
   168  		To:       &ethAddr,
   169  		Value:    tsf.amount,
   170  		Data:     tsf.payload,
   171  	}), nil
   172  }