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 }