github.com/iotexproject/iotex-core@v1.14.1-rc1/action/execution.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 "encoding/hex" 10 "math/big" 11 12 "github.com/ethereum/go-ethereum/common" 13 "github.com/ethereum/go-ethereum/core/types" 14 "github.com/iotexproject/iotex-address/address" 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/addrutil" 20 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 21 "github.com/iotexproject/iotex-core/pkg/version" 22 ) 23 24 // const 25 const ( 26 EmptyAddress = "" 27 ExecutionDataGas uint64 = 100 // per-byte execution data gas 28 ExecutionBaseIntrinsicGas uint64 = 10000 // base intrinsic gas for execution 29 TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list 30 TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list 31 ) 32 33 var ( 34 _ hasDestination = (*Execution)(nil) 35 _ EthCompatibleAction = (*Execution)(nil) 36 ) 37 38 // Execution defines the struct of account-based contract execution 39 type Execution struct { 40 AbstractAction 41 42 contract string 43 amount *big.Int 44 data []byte 45 accessList types.AccessList 46 } 47 48 // NewExecution returns an Execution instance (w/o access list) 49 func NewExecution( 50 contractAddress string, 51 nonce uint64, 52 amount *big.Int, 53 gasLimit uint64, 54 gasPrice *big.Int, 55 data []byte, 56 ) (*Execution, error) { 57 return &Execution{ 58 AbstractAction: AbstractAction{ 59 version: version.ProtocolVersion, 60 nonce: nonce, 61 gasLimit: gasLimit, 62 gasPrice: gasPrice, 63 }, 64 contract: contractAddress, 65 amount: amount, 66 data: data, 67 }, nil 68 } 69 70 // NewExecutionWithAccessList returns an Execution instance with access list 71 func NewExecutionWithAccessList( 72 contractAddress string, 73 nonce uint64, 74 amount *big.Int, 75 gasLimit uint64, 76 gasPrice *big.Int, 77 data []byte, 78 list types.AccessList, 79 ) (*Execution, error) { 80 return &Execution{ 81 AbstractAction: AbstractAction{ 82 version: version.ProtocolVersion, 83 nonce: nonce, 84 gasLimit: gasLimit, 85 gasPrice: gasPrice, 86 }, 87 contract: contractAddress, 88 amount: amount, 89 data: data, 90 accessList: list, 91 }, nil 92 } 93 94 // Contract returns a contract address 95 func (ex *Execution) Contract() string { return ex.contract } 96 97 // Destination returns a contract address 98 func (ex *Execution) Destination() string { return ex.contract } 99 100 // Recipient is same as Contract() 101 func (ex *Execution) Recipient() string { return ex.contract } 102 103 // Amount returns the amount 104 func (ex *Execution) Amount() *big.Int { return ex.amount } 105 106 // Data returns the data bytes 107 func (ex *Execution) Data() []byte { return ex.data } 108 109 // Payload is same as Data() 110 func (ex *Execution) Payload() []byte { return ex.data } 111 112 // AccessList returns the access list 113 func (ex *Execution) AccessList() types.AccessList { return ex.accessList } 114 115 func toAccessListProto(list types.AccessList) []*iotextypes.AccessTuple { 116 if len(list) == 0 { 117 return nil 118 } 119 proto := make([]*iotextypes.AccessTuple, len(list)) 120 for i, v := range list { 121 proto[i] = &iotextypes.AccessTuple{} 122 proto[i].Address = hex.EncodeToString(v.Address.Bytes()) 123 if numKey := len(v.StorageKeys); numKey > 0 { 124 proto[i].StorageKeys = make([]string, numKey) 125 for j, key := range v.StorageKeys { 126 proto[i].StorageKeys[j] = hex.EncodeToString(key.Bytes()) 127 } 128 } 129 } 130 return proto 131 } 132 133 func fromAccessListProto(list []*iotextypes.AccessTuple) types.AccessList { 134 if len(list) == 0 { 135 return nil 136 } 137 accessList := make(types.AccessList, len(list)) 138 for i, v := range list { 139 accessList[i].Address = common.HexToAddress(v.Address) 140 if numKey := len(v.StorageKeys); numKey > 0 { 141 accessList[i].StorageKeys = make([]common.Hash, numKey) 142 for j, key := range v.StorageKeys { 143 accessList[i].StorageKeys[j] = common.HexToHash(key) 144 } 145 } 146 } 147 return accessList 148 } 149 150 // TotalSize returns the total size of this Execution 151 func (ex *Execution) TotalSize() uint32 { 152 size := ex.BasicActionSize() 153 if ex.amount != nil && len(ex.amount.Bytes()) > 0 { 154 size += uint32(len(ex.amount.Bytes())) 155 } 156 // 65 is the pubkey size 157 return size + uint32(len(ex.data)) + 65 158 } 159 160 // Serialize returns a raw byte stream of this Transfer 161 func (ex *Execution) Serialize() []byte { 162 return byteutil.Must(proto.Marshal(ex.Proto())) 163 } 164 165 // Proto converts Execution to protobuf's Execution 166 func (ex *Execution) Proto() *iotextypes.Execution { 167 act := &iotextypes.Execution{ 168 Contract: ex.contract, 169 Data: ex.data, 170 } 171 if ex.amount != nil && len(ex.amount.String()) > 0 { 172 act.Amount = ex.amount.String() 173 } 174 act.AccessList = toAccessListProto(ex.accessList) 175 return act 176 } 177 178 // LoadProto converts a protobuf's Execution to Execution 179 func (ex *Execution) LoadProto(pbAct *iotextypes.Execution) error { 180 if pbAct == nil { 181 return ErrNilProto 182 } 183 if ex == nil { 184 return ErrNilAction 185 } 186 *ex = Execution{} 187 188 ex.contract = pbAct.GetContract() 189 if pbAct.GetAmount() == "" { 190 ex.amount = big.NewInt(0) 191 } else { 192 amount, ok := new(big.Int).SetString(pbAct.GetAmount(), 10) 193 if !ok { 194 return errors.Errorf("invalid amount %s", pbAct.GetAmount()) 195 } 196 ex.amount = amount 197 } 198 ex.data = pbAct.GetData() 199 ex.accessList = fromAccessListProto(pbAct.AccessList) 200 return nil 201 } 202 203 // IntrinsicGas returns the intrinsic gas of an execution 204 func (ex *Execution) IntrinsicGas() (uint64, error) { 205 gas, err := CalculateIntrinsicGas(ExecutionBaseIntrinsicGas, ExecutionDataGas, uint64(len(ex.Data()))) 206 if err != nil { 207 return gas, err 208 } 209 if len(ex.accessList) > 0 { 210 gas += uint64(len(ex.accessList)) * TxAccessListAddressGas 211 gas += uint64(ex.accessList.StorageKeys()) * TxAccessListStorageKeyGas 212 } 213 return gas, nil 214 } 215 216 // Cost returns the cost of an execution 217 func (ex *Execution) Cost() (*big.Int, error) { 218 maxExecFee := big.NewInt(0).Mul(ex.GasPrice(), big.NewInt(0).SetUint64(ex.GasLimit())) 219 return big.NewInt(0).Add(ex.Amount(), maxExecFee), nil 220 } 221 222 // SanityCheck validates the variables in the action 223 func (ex *Execution) SanityCheck() error { 224 // Reject execution of negative amount 225 if ex.Amount().Sign() < 0 { 226 return errors.Wrap(ErrInvalidAmount, "negative value") 227 } 228 // check if contract's address is valid 229 if ex.Contract() != EmptyAddress { 230 if _, err := address.FromString(ex.Contract()); err != nil { 231 return errors.Wrapf(err, "error when validating contract's address %s", ex.Contract()) 232 } 233 } 234 return ex.AbstractAction.SanityCheck() 235 } 236 237 // ToEthTx converts action to eth-compatible tx 238 func (ex *Execution) ToEthTx(evmNetworkID uint32) (*types.Transaction, error) { 239 var ethAddr *common.Address 240 if ex.contract != EmptyAddress { 241 addr, err := addrutil.IoAddrToEvmAddr(ex.contract) 242 if err != nil { 243 return nil, err 244 } 245 ethAddr = &addr 246 } 247 if len(ex.accessList) > 0 { 248 return types.NewTx(&types.AccessListTx{ 249 ChainID: big.NewInt(int64(evmNetworkID)), 250 Nonce: ex.Nonce(), 251 GasPrice: ex.GasPrice(), 252 Gas: ex.GasLimit(), 253 To: ethAddr, 254 Value: ex.amount, 255 Data: ex.data, 256 AccessList: ex.accessList, 257 }), nil 258 } 259 return types.NewTx(&types.LegacyTx{ 260 Nonce: ex.Nonce(), 261 GasPrice: ex.GasPrice(), 262 Gas: ex.GasLimit(), 263 To: ethAddr, 264 Value: ex.amount, 265 Data: ex.data, 266 }), nil 267 }