github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/account/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 account
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  
    12  	"github.com/iotexproject/iotex-address/address"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/iotexproject/iotex-core/action"
    16  	"github.com/iotexproject/iotex-core/action/protocol"
    17  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    18  	"github.com/iotexproject/iotex-core/state"
    19  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    20  )
    21  
    22  // TransferSizeLimit is the maximum size of transfer allowed
    23  const TransferSizeLimit = 32 * 1024
    24  
    25  // handleTransfer handles a transfer
    26  func (p *Protocol) handleTransfer(ctx context.Context, tsf *action.Transfer, sm protocol.StateManager) (*action.Receipt, error) {
    27  	var (
    28  		fCtx      = protocol.MustGetFeatureCtx(ctx)
    29  		actionCtx = protocol.MustGetActionCtx(ctx)
    30  		blkCtx    = protocol.MustGetBlockCtx(ctx)
    31  	)
    32  	accountCreationOpts := []state.AccountCreationOption{}
    33  	if fCtx.CreateLegacyNonceAccount {
    34  		accountCreationOpts = append(accountCreationOpts, state.LegacyNonceAccountTypeOption())
    35  	}
    36  	// check sender
    37  	sender, err := accountutil.LoadOrCreateAccount(sm, actionCtx.Caller, accountCreationOpts...)
    38  	if err != nil {
    39  		return nil, errors.Wrapf(err, "failed to load or create the account of sender %s", actionCtx.Caller.String())
    40  	}
    41  
    42  	gasFee := big.NewInt(0).Mul(tsf.GasPrice(), big.NewInt(0).SetUint64(actionCtx.IntrinsicGas))
    43  	if !sender.HasSufficientBalance(big.NewInt(0).Add(tsf.Amount(), gasFee)) {
    44  		return nil, errors.Wrapf(
    45  			state.ErrNotEnoughBalance,
    46  			"sender %s balance %s, required amount %s",
    47  			actionCtx.Caller.String(),
    48  			sender.Balance,
    49  			big.NewInt(0).Add(tsf.Amount(), gasFee),
    50  		)
    51  	}
    52  
    53  	var depositLog *action.TransactionLog
    54  	if !fCtx.FixDoubleChargeGas {
    55  		// charge sender gas
    56  		if err := sender.SubBalance(gasFee); err != nil {
    57  			return nil, errors.Wrapf(err, "failed to charge the gas for sender %s", actionCtx.Caller.String())
    58  		}
    59  		if p.depositGas != nil {
    60  			depositLog, err = p.depositGas(ctx, sm, gasFee)
    61  			if err != nil {
    62  				return nil, err
    63  			}
    64  		}
    65  	}
    66  
    67  	if fCtx.FixGasAndNonceUpdate || tsf.Nonce() != 0 {
    68  		// update sender Nonce
    69  		if err := sender.SetPendingNonce(tsf.Nonce() + 1); err != nil {
    70  			return nil, errors.Wrapf(err, "failed to update pending nonce of sender %s", actionCtx.Caller.String())
    71  		}
    72  	}
    73  
    74  	var recipientAddr address.Address
    75  	if fCtx.TolerateLegacyAddress {
    76  		recipientAddr, err = address.FromStringLegacy(tsf.Recipient())
    77  	} else {
    78  		recipientAddr, err = address.FromString(tsf.Recipient())
    79  	}
    80  	if err != nil {
    81  		return nil, errors.Wrapf(err, "failed to decode recipient address %s", tsf.Recipient())
    82  	}
    83  	recipientAcct, err := accountutil.LoadAccount(sm, recipientAddr, accountCreationOpts...)
    84  	if err != nil {
    85  		return nil, errors.Wrapf(err, "failed to load address %s", tsf.Recipient())
    86  	}
    87  	if recipientAcct.IsContract() {
    88  		// put updated sender's state to trie
    89  		if err := accountutil.StoreAccount(sm, actionCtx.Caller, sender); err != nil {
    90  			return nil, errors.Wrap(err, "failed to update pending account changes to trie")
    91  		}
    92  		if fCtx.FixDoubleChargeGas {
    93  			if p.depositGas != nil {
    94  				depositLog, err = p.depositGas(ctx, sm, gasFee)
    95  				if err != nil {
    96  					return nil, err
    97  				}
    98  			}
    99  		}
   100  		receipt := &action.Receipt{
   101  			Status:          uint64(iotextypes.ReceiptStatus_Failure),
   102  			BlockHeight:     blkCtx.BlockHeight,
   103  			ActionHash:      actionCtx.ActionHash,
   104  			GasConsumed:     actionCtx.IntrinsicGas,
   105  			ContractAddress: p.addr.String(),
   106  		}
   107  		receipt.AddTransactionLogs(depositLog)
   108  		return receipt, nil
   109  	}
   110  
   111  	// update sender Balance
   112  	if err := sender.SubBalance(tsf.Amount()); err != nil {
   113  		return nil, errors.Wrapf(err, "failed to update the Balance of sender %s", actionCtx.Caller.String())
   114  	}
   115  
   116  	// put updated sender's state to trie
   117  	if err := accountutil.StoreAccount(sm, actionCtx.Caller, sender); err != nil {
   118  		return nil, errors.Wrap(err, "failed to update pending account changes to trie")
   119  	}
   120  
   121  	// check recipient
   122  	recipient, err := accountutil.LoadOrCreateAccount(sm, recipientAddr, accountCreationOpts...)
   123  	if err != nil {
   124  		return nil, errors.Wrapf(err, "failed to load or create the account of recipient %s", tsf.Recipient())
   125  	}
   126  	if err := recipient.AddBalance(tsf.Amount()); err != nil {
   127  		return nil, errors.Wrapf(err, "failed to add balance %s", tsf.Amount())
   128  	}
   129  	// put updated recipient's state to trie
   130  	if err := accountutil.StoreAccount(sm, recipientAddr, recipient); err != nil {
   131  		return nil, errors.Wrap(err, "failed to update pending account changes to trie")
   132  	}
   133  
   134  	if fCtx.FixDoubleChargeGas {
   135  		if p.depositGas != nil {
   136  			depositLog, err = p.depositGas(ctx, sm, gasFee)
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  		}
   141  	}
   142  
   143  	receipt := &action.Receipt{
   144  		Status:          uint64(iotextypes.ReceiptStatus_Success),
   145  		BlockHeight:     blkCtx.BlockHeight,
   146  		ActionHash:      actionCtx.ActionHash,
   147  		GasConsumed:     actionCtx.IntrinsicGas,
   148  		ContractAddress: p.addr.String(),
   149  	}
   150  	receipt.AddTransactionLogs(&action.TransactionLog{
   151  		Type:      iotextypes.TransactionLogType_NATIVE_TRANSFER,
   152  		Sender:    actionCtx.Caller.String(),
   153  		Recipient: tsf.Recipient(),
   154  		Amount:    tsf.Amount(),
   155  	}, depositLog)
   156  
   157  	return receipt, nil
   158  }
   159  
   160  // validateTransfer validates a transfer
   161  func (p *Protocol) validateTransfer(ctx context.Context, tsf *action.Transfer) error {
   162  	// Reject oversized transfer
   163  	if tsf.TotalSize() > TransferSizeLimit {
   164  		return action.ErrOversizedData
   165  	}
   166  	var (
   167  		fCtx = protocol.MustGetFeatureCtx(ctx)
   168  		err  error
   169  	)
   170  	if fCtx.TolerateLegacyAddress {
   171  		_, err = address.FromStringLegacy(tsf.Recipient())
   172  	} else {
   173  		_, err = address.FromString(tsf.Recipient())
   174  	}
   175  	return err
   176  }