github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/account/transfer_test.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  	"testing"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/pkg/errors"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/iotexproject/iotex-address/address"
    18  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    19  
    20  	"github.com/iotexproject/iotex-core/action"
    21  	"github.com/iotexproject/iotex-core/action/protocol"
    22  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    23  	"github.com/iotexproject/iotex-core/action/protocol/rewarding"
    24  	"github.com/iotexproject/iotex-core/blockchain/block"
    25  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    26  	"github.com/iotexproject/iotex-core/state"
    27  	"github.com/iotexproject/iotex-core/test/identityset"
    28  	"github.com/iotexproject/iotex-core/testutil"
    29  	"github.com/iotexproject/iotex-core/testutil/testdb"
    30  )
    31  
    32  func TestProtocol_ValidateTransfer(t *testing.T) {
    33  	require := require.New(t)
    34  	p := NewProtocol(rewarding.DepositGas)
    35  	t.Run("invalid transfer", func(t *testing.T) {
    36  		tsf, err := action.NewTransfer(uint64(1), big.NewInt(1), "2", make([]byte, 32683), uint64(0), big.NewInt(0))
    37  		require.NoError(err)
    38  		tsf1, err := action.NewTransfer(uint64(1), big.NewInt(1), "2", nil, uint64(0), big.NewInt(0))
    39  		require.NoError(err)
    40  		g := genesis.Default
    41  		ctx := protocol.WithFeatureCtx(genesis.WithGenesisContext(protocol.WithBlockCtx(context.Background(), protocol.BlockCtx{
    42  			BlockHeight: g.NewfoundlandBlockHeight,
    43  		}), g))
    44  		for _, v := range []struct {
    45  			tsf *action.Transfer
    46  			err error
    47  		}{
    48  			{tsf, action.ErrOversizedData},
    49  			{tsf1, address.ErrInvalidAddr},
    50  		} {
    51  			require.Equal(v.err, errors.Cause(p.Validate(ctx, v.tsf, nil)))
    52  		}
    53  	})
    54  }
    55  
    56  func TestProtocol_HandleTransfer(t *testing.T) {
    57  	require := require.New(t)
    58  
    59  	ctrl := gomock.NewController(t)
    60  	sm := testdb.NewMockStateManager(ctrl)
    61  
    62  	// set-up protocol and genesis states
    63  	p := NewProtocol(rewarding.DepositGas)
    64  	reward := rewarding.NewProtocol(genesis.Default.Rewarding)
    65  	registry := protocol.NewRegistry()
    66  	require.NoError(reward.Register(registry))
    67  	chainCtx := genesis.WithGenesisContext(
    68  		protocol.WithRegistry(context.Background(), registry),
    69  		genesis.Default,
    70  	)
    71  	ctx := protocol.WithBlockCtx(chainCtx, protocol.BlockCtx{})
    72  	ctx = protocol.WithFeatureCtx(ctx)
    73  	require.NoError(reward.CreateGenesisStates(ctx, sm))
    74  
    75  	// initial deposit to alfa and charlie (as a contract)
    76  	alfa := identityset.Address(28)
    77  	bravo := identityset.Address(29)
    78  	charlie := identityset.Address(30)
    79  	acct1, err := state.NewAccount(state.LegacyNonceAccountTypeOption())
    80  	require.NoError(err)
    81  	require.NoError(acct1.AddBalance(big.NewInt(50005)))
    82  	require.NoError(accountutil.StoreAccount(sm, alfa, acct1))
    83  	acct2, err := state.NewAccount()
    84  	require.NoError(err)
    85  	acct2.CodeHash = []byte("codeHash")
    86  	require.NoError(accountutil.StoreAccount(sm, charlie, acct2))
    87  
    88  	tests := []struct {
    89  		caller      address.Address
    90  		nonce       uint64
    91  		amount      *big.Int
    92  		recipient   string
    93  		gasLimit    uint64
    94  		gasPrice    *big.Int
    95  		isContract  bool
    96  		err         error
    97  		status      uint64
    98  		contractLog uint64
    99  	}{
   100  		{
   101  			alfa, 1, big.NewInt(2), bravo.String(), 10000, big.NewInt(1), false, nil, uint64(iotextypes.ReceiptStatus_Success), 2,
   102  		},
   103  		// transfer to contract address only charges gas fee
   104  		{
   105  			alfa, 2, big.NewInt(20), charlie.String(), 10000, big.NewInt(1), true, nil, uint64(iotextypes.ReceiptStatus_Failure), 1,
   106  		},
   107  		// not enough balance
   108  		{
   109  			alfa, 3, big.NewInt(30000), bravo.String(), 10000, big.NewInt(1), false, state.ErrNotEnoughBalance, uint64(iotextypes.ReceiptStatus_Failure), 1,
   110  		},
   111  	}
   112  
   113  	for _, v := range tests {
   114  		tsf, err := action.NewTransfer(v.nonce, v.amount, v.recipient, []byte{}, v.gasLimit, v.gasPrice)
   115  		require.NoError(err)
   116  		gas, err := tsf.IntrinsicGas()
   117  		require.NoError(err)
   118  
   119  		ctx := protocol.WithActionCtx(chainCtx, protocol.ActionCtx{
   120  			Caller:       v.caller,
   121  			IntrinsicGas: gas,
   122  			Nonce:        v.nonce,
   123  		})
   124  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   125  			BlockHeight: 1,
   126  			Producer:    identityset.Address(27),
   127  			GasLimit:    testutil.TestGasLimit,
   128  		})
   129  
   130  		sender, err := accountutil.AccountState(ctx, sm, v.caller)
   131  		require.NoError(err)
   132  		addr, err := address.FromString(v.recipient)
   133  		require.NoError(err)
   134  		recipient, err := accountutil.AccountState(ctx, sm, addr)
   135  		require.NoError(err)
   136  		gasFee := new(big.Int).Mul(v.gasPrice, new(big.Int).SetUint64(gas))
   137  
   138  		ctx = protocol.WithFeatureCtx(ctx)
   139  		receipt, err := p.Handle(ctx, tsf, sm)
   140  		require.Equal(v.err, errors.Cause(err))
   141  		if err != nil {
   142  			require.Nil(receipt)
   143  			// sender balance/nonce remains the same in case of error
   144  			newSender, err := accountutil.AccountState(ctx, sm, v.caller)
   145  			require.NoError(err)
   146  			require.Equal(sender.Balance, newSender.Balance)
   147  			require.Equal(sender.PendingNonce(), newSender.PendingNonce())
   148  			continue
   149  		}
   150  		require.Equal(v.status, receipt.Status)
   151  
   152  		// amount is transferred only upon success and for non-contract recipient
   153  		if receipt.Status == uint64(iotextypes.ReceiptStatus_Success) && !v.isContract {
   154  			gasFee.Add(gasFee, v.amount)
   155  			// verify recipient
   156  			addr, err := address.FromString(v.recipient)
   157  			require.NoError(err)
   158  			newRecipient, err := accountutil.AccountState(ctx, sm, addr)
   159  			require.NoError(err)
   160  			require.NoError(recipient.AddBalance(v.amount))
   161  			require.Equal(recipient.Balance, newRecipient.Balance)
   162  		}
   163  		// verify sender balance/nonce
   164  		newSender, err := accountutil.AccountState(ctx, sm, v.caller)
   165  		require.NoError(err)
   166  		require.NoError(sender.SubBalance(gasFee))
   167  		require.Equal(sender.Balance, newSender.Balance)
   168  		require.Equal(v.nonce+1, newSender.PendingNonce())
   169  
   170  		// verify transaction log
   171  		tLog := block.ReceiptTransactionLog(receipt)
   172  		if tLog != nil {
   173  			require.NotNil(tLog)
   174  			pbLog := tLog.Proto()
   175  			require.EqualValues(v.contractLog, pbLog.NumTransactions)
   176  			// TODO: verify gas transaction log
   177  			if len(pbLog.Transactions) > 1 {
   178  				rec := pbLog.Transactions[0]
   179  				require.Equal(v.amount.String(), rec.Amount)
   180  				require.Equal(v.caller.String(), rec.Sender)
   181  				require.Equal(v.recipient, rec.Recipient)
   182  				require.Equal(iotextypes.TransactionLogType_NATIVE_TRANSFER, rec.Type)
   183  			}
   184  		}
   185  	}
   186  }