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: ðAddr, 169 Value: tsf.amount, 170 Data: tsf.payload, 171 }), nil 172 }