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  }