github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/handlers_test.go (about)

     1  // Copyright (c) 2022 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 staking
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"math"
    13  	"math/big"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/agiledragon/gomonkey/v2"
    18  	"github.com/golang/mock/gomock"
    19  	"github.com/mohae/deepcopy"
    20  	"github.com/pkg/errors"
    21  	"github.com/stretchr/testify/require"
    22  
    23  	"github.com/iotexproject/go-pkgs/crypto"
    24  	"github.com/iotexproject/iotex-address/address"
    25  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    26  
    27  	"github.com/iotexproject/iotex-core/action"
    28  	"github.com/iotexproject/iotex-core/action/protocol"
    29  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    30  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    31  	"github.com/iotexproject/iotex-core/pkg/unit"
    32  	"github.com/iotexproject/iotex-core/state"
    33  	"github.com/iotexproject/iotex-core/test/identityset"
    34  	"github.com/iotexproject/iotex-core/testutil/testdb"
    35  )
    36  
    37  const (
    38  	_reclaim = "This is to certify I am transferring the ownership of said bucket to said recipient on IoTeX blockchain"
    39  )
    40  
    41  // Delete should only be used by test
    42  func (m *CandidateCenter) deleteForTestOnly(owner address.Address) {
    43  	if owner == nil {
    44  		return
    45  	}
    46  
    47  	if _, hit := m.base.getByOwner(owner.String()); hit {
    48  		m.base.delete(owner)
    49  		if !m.change.containsOwner(owner) {
    50  			m.size--
    51  			return
    52  		}
    53  	}
    54  
    55  	if m.change.containsOwner(owner) {
    56  		if owner != nil {
    57  			candidates := []*Candidate{}
    58  			for _, c := range m.change.candidates {
    59  				if c.Owner.String() != owner.String() {
    60  					candidates = append(candidates, c)
    61  				}
    62  			}
    63  			m.change.candidates = candidates
    64  			delete(m.change.dirty, owner.String())
    65  			return
    66  		}
    67  		m.size--
    68  	}
    69  }
    70  
    71  func TestProtocol_HandleCreateStake(t *testing.T) {
    72  	require := require.New(t)
    73  
    74  	ctrl := gomock.NewController(t)
    75  	sm := testdb.NewMockStateManager(ctrl)
    76  	csm := newCandidateStateManager(sm)
    77  	csr := newCandidateStateReader(sm)
    78  	_, err := sm.PutState(
    79  		&totalBucketCount{count: 0},
    80  		protocol.NamespaceOption(_stakingNameSpace),
    81  		protocol.KeyOption(TotalBucketKey),
    82  	)
    83  	require.NoError(err)
    84  
    85  	// create protocol
    86  	p, err := NewProtocol(depositGas, &BuilderConfig{
    87  		Staking:                  genesis.Default.Staking,
    88  		PersistStakingPatchBlock: math.MaxUint64,
    89  	}, nil, nil, genesis.Default.GreenlandBlockHeight)
    90  	require.NoError(err)
    91  
    92  	// set up candidate
    93  	candidate := testCandidates[0].d.Clone()
    94  	require.NoError(csm.putCandidate(candidate))
    95  	candidateName := candidate.Name
    96  	candidateAddr := candidate.Owner
    97  	ctx := genesis.WithGenesisContext(context.Background(), genesis.Default)
    98  	ctx = protocol.WithFeatureWithHeightCtx(ctx)
    99  	v, err := p.Start(ctx, sm)
   100  	require.NoError(err)
   101  	cc, ok := v.(*ViewData)
   102  	require.True(ok)
   103  	require.NoError(sm.WriteView(_protocolID, cc))
   104  
   105  	stakerAddr := identityset.Address(1)
   106  	tests := []struct {
   107  		// action fields
   108  		initBalance int64
   109  		candName    string
   110  		amount      string
   111  		duration    uint32
   112  		autoStake   bool
   113  		gasPrice    *big.Int
   114  		gasLimit    uint64
   115  		nonce       uint64
   116  		// block context
   117  		blkHeight    uint64
   118  		blkTimestamp time.Time
   119  		blkGasLimit  uint64
   120  		// expected result
   121  		err    error
   122  		status iotextypes.ReceiptStatus
   123  	}{
   124  		{
   125  			10,
   126  			candidateName,
   127  			"100000000000000000000",
   128  			1,
   129  			false,
   130  			big.NewInt(unit.Qev),
   131  			10000,
   132  			1,
   133  			1,
   134  			time.Now(),
   135  			10000,
   136  			nil,
   137  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   138  		},
   139  		{
   140  			100,
   141  			"notExist",
   142  			"100000000000000000000",
   143  			1,
   144  			false,
   145  			big.NewInt(unit.Qev),
   146  			10000,
   147  			2,
   148  			1,
   149  			time.Now(),
   150  			10000,
   151  			action.ErrInvalidCanName,
   152  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
   153  		},
   154  		{
   155  			101,
   156  			candidateName,
   157  			"10000000000000000000",
   158  			1,
   159  			false,
   160  			big.NewInt(unit.Qev),
   161  			10000,
   162  			2,
   163  			1,
   164  			time.Now(),
   165  			10000,
   166  			action.ErrInvalidAmount,
   167  			iotextypes.ReceiptStatus_Failure,
   168  		},
   169  		{
   170  			101,
   171  			candidateName,
   172  			"100000000000000000000",
   173  			1,
   174  			false,
   175  			big.NewInt(unit.Qev),
   176  			10000,
   177  			2,
   178  			1,
   179  			time.Now(),
   180  			10000,
   181  			nil,
   182  			iotextypes.ReceiptStatus_Success,
   183  		},
   184  	}
   185  
   186  	for _, test := range tests {
   187  		require.NoError(setupAccount(sm, stakerAddr, test.initBalance))
   188  		ctx := protocol.WithActionCtx(ctx, protocol.ActionCtx{
   189  			Caller:       stakerAddr,
   190  			GasPrice:     test.gasPrice,
   191  			IntrinsicGas: test.gasLimit,
   192  			Nonce:        test.nonce,
   193  		})
   194  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   195  			BlockHeight:    test.blkHeight,
   196  			BlockTimeStamp: test.blkTimestamp,
   197  			GasLimit:       test.blkGasLimit,
   198  		})
   199  		act, err := action.NewCreateStake(test.nonce, test.candName, test.amount, test.duration, test.autoStake,
   200  			nil, test.gasLimit, test.gasPrice)
   201  		require.NoError(err)
   202  		err = p.Validate(ctx, act, sm)
   203  		if test.err != nil {
   204  			require.EqualError(test.err, errors.Cause(err).Error())
   205  			continue
   206  		}
   207  		ctx = protocol.WithFeatureCtx(ctx)
   208  		r, err := p.Handle(ctx, act, sm)
   209  		require.NoError(err)
   210  		require.Equal(uint64(test.status), r.Status)
   211  
   212  		if test.status == iotextypes.ReceiptStatus_Success {
   213  			// check the special create bucket log
   214  			tLogs := r.TransactionLogs()
   215  			require.Equal(1, len(tLogs))
   216  			cLog := tLogs[0]
   217  			require.Equal(stakerAddr.String(), cLog.Sender)
   218  			require.Equal(address.StakingBucketPoolAddr, cLog.Recipient)
   219  			require.Equal(test.amount, cLog.Amount.String())
   220  
   221  			// test bucket index and bucket
   222  			bucketIndices, _, err := csr.candBucketIndices(candidateAddr)
   223  			require.NoError(err)
   224  			require.Equal(1, len(*bucketIndices))
   225  			bucketIndices, _, err = csr.voterBucketIndices(stakerAddr)
   226  			require.NoError(err)
   227  			require.Equal(1, len(*bucketIndices))
   228  			indices := *bucketIndices
   229  			bucket, err := csr.getBucket(indices[0])
   230  			require.NoError(err)
   231  			require.Equal(candidateAddr, bucket.Candidate)
   232  			require.Equal(stakerAddr, bucket.Owner)
   233  			require.Equal(test.amount, bucket.StakedAmount.String())
   234  
   235  			// test candidate
   236  			candidate, _, err := csr.getCandidate(candidateAddr)
   237  			require.NoError(err)
   238  			require.LessOrEqual(test.amount, candidate.Votes.String())
   239  			csm, err := NewCandidateStateManager(sm, false)
   240  			require.NoError(err)
   241  			candidate = csm.GetByOwner(candidateAddr)
   242  			require.NotNil(candidate)
   243  			require.LessOrEqual(test.amount, candidate.Votes.String())
   244  
   245  			// test staker's account
   246  			caller, err := accountutil.LoadAccount(sm, stakerAddr)
   247  			require.NoError(err)
   248  			actCost, err := act.Cost()
   249  			require.NoError(err)
   250  			require.Equal(unit.ConvertIotxToRau(test.initBalance), big.NewInt(0).Add(caller.Balance, actCost))
   251  			require.Equal(test.nonce+1, caller.PendingNonce())
   252  		}
   253  	}
   254  }
   255  
   256  func TestProtocol_HandleCandidateRegister(t *testing.T) {
   257  	require := require.New(t)
   258  	ctrl := gomock.NewController(t)
   259  	sm, p, _, _ := initAll(t, ctrl)
   260  	csr := newCandidateStateReader(sm)
   261  	tests := []struct {
   262  		initBalance     int64
   263  		caller          address.Address
   264  		nonce           uint64
   265  		name            string
   266  		operatorAddrStr string
   267  		rewardAddrStr   string
   268  		ownerAddrStr    string
   269  		amountStr       string
   270  		votesStr        string
   271  		duration        uint32
   272  		autoStake       bool
   273  		payload         []byte
   274  		gasLimit        uint64
   275  		blkGasLimit     uint64
   276  		gasPrice        *big.Int
   277  		newProtocol     bool
   278  		err             error
   279  		status          iotextypes.ReceiptStatus
   280  	}{
   281  		// fetchCaller,ErrNotEnoughBalance
   282  		{
   283  			1200000,
   284  			identityset.Address(27),
   285  			1,
   286  			"test",
   287  			identityset.Address(28).String(),
   288  			identityset.Address(29).String(),
   289  			identityset.Address(30).String(),
   290  			"1200000000000000000000000",
   291  			"",
   292  			uint32(10000),
   293  			false,
   294  			[]byte("payload"),
   295  			uint64(1000000),
   296  			uint64(1000000),
   297  			big.NewInt(1000),
   298  			true,
   299  			nil,
   300  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   301  		},
   302  		// owner address is nil
   303  		{
   304  			1201000,
   305  			identityset.Address(27),
   306  			1,
   307  			"test",
   308  			identityset.Address(28).String(),
   309  			identityset.Address(29).String(),
   310  			"",
   311  			"1200000000000000000000000",
   312  			"1806204150552640363969204",
   313  			uint32(10000),
   314  			false,
   315  			nil,
   316  			uint64(1000000),
   317  			uint64(1000000),
   318  			big.NewInt(1),
   319  			true,
   320  			nil,
   321  			iotextypes.ReceiptStatus_Success,
   322  		},
   323  		// Upsert,check collision
   324  		{
   325  			1201000,
   326  			identityset.Address(27),
   327  			2,
   328  			"test",
   329  			identityset.Address(28).String(),
   330  			identityset.Address(29).String(),
   331  			identityset.Address(30).String(),
   332  			"1200000000000000000000000",
   333  			"",
   334  			uint32(10000),
   335  			false,
   336  			[]byte("payload"),
   337  			uint64(1000000),
   338  			uint64(1000000),
   339  			big.NewInt(1000),
   340  			false,
   341  			nil,
   342  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   343  		},
   344  		// invalid amount
   345  		{
   346  			1201000,
   347  			identityset.Address(27),
   348  			1,
   349  			"test",
   350  			identityset.Address(28).String(),
   351  			identityset.Address(29).String(),
   352  			identityset.Address(30).String(),
   353  			"120000000000000000000000",
   354  			"1806204150552640363969204",
   355  			uint32(10000),
   356  			false,
   357  			nil,
   358  			uint64(1000000),
   359  			uint64(1000000),
   360  			big.NewInt(1),
   361  			true,
   362  			action.ErrInvalidAmount,
   363  			iotextypes.ReceiptStatus_Failure,
   364  		},
   365  		// invalid candidate name
   366  		{
   367  			1201000,
   368  			identityset.Address(27),
   369  			1,
   370  			"!invalid",
   371  			identityset.Address(28).String(),
   372  			identityset.Address(29).String(),
   373  			identityset.Address(30).String(),
   374  			"1200000000000000000000000",
   375  			"1806204150552640363969204",
   376  			uint32(10000),
   377  			false,
   378  			nil,
   379  			uint64(1000000),
   380  			uint64(1000000),
   381  			big.NewInt(1),
   382  			true,
   383  			action.ErrInvalidCanName,
   384  			iotextypes.ReceiptStatus_Failure,
   385  		},
   386  		// success for the following test
   387  		{
   388  			1201000,
   389  			identityset.Address(27),
   390  			1,
   391  			"test",
   392  			identityset.Address(28).String(),
   393  			identityset.Address(29).String(),
   394  			identityset.Address(30).String(),
   395  			"1200000000000000000000000",
   396  			"1806204150552640363969204",
   397  			uint32(10000),
   398  			false,
   399  			nil,
   400  			uint64(1000000),
   401  			uint64(1000000),
   402  			big.NewInt(1),
   403  			true,
   404  			nil,
   405  			iotextypes.ReceiptStatus_Success,
   406  		},
   407  		// act.OwnerAddress() is not nil,existing owner, but selfstake is not 0
   408  		{
   409  			1201000,
   410  			identityset.Address(27),
   411  			2,
   412  			"test2",
   413  			identityset.Address(10).String(),
   414  			identityset.Address(10).String(),
   415  			identityset.Address(30).String(), "1200000000000000000000000",
   416  			"1200000000000000000000000",
   417  			uint32(10000),
   418  			false,
   419  			[]byte("payload"),
   420  			1000000,
   421  			1000000,
   422  			big.NewInt(1),
   423  			false,
   424  			nil,
   425  			iotextypes.ReceiptStatus_ErrCandidateAlreadyExist,
   426  		},
   427  		// act.OwnerAddress() is not nil,existing candidate, collide with existing name,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist
   428  		// act.OwnerAddress() is not nil,existing candidate, collide with existing operator,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist
   429  		// act.OwnerAddress() is not nil,new candidate, collide with existing name
   430  		{
   431  			1201000,
   432  			identityset.Address(27),
   433  			3,
   434  			"test",
   435  			identityset.Address(10).String(),
   436  			identityset.Address(10).String(),
   437  			identityset.Address(29).String(),
   438  			"1200000000000000000000000",
   439  			"1200000000000000000000000",
   440  			uint32(10000),
   441  			false,
   442  			[]byte("payload"),
   443  			1000000,
   444  			1000000,
   445  			big.NewInt(unit.Qev),
   446  			false,
   447  			nil,
   448  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   449  		},
   450  		// act.OwnerAddress() is not nil,new candidate, collide with existing operator
   451  		{
   452  			1201000,
   453  			identityset.Address(27),
   454  			4,
   455  			"test2",
   456  			identityset.Address(28).String(),
   457  			identityset.Address(10).String(),
   458  			identityset.Address(29).String(),
   459  			"1200000000000000000000000",
   460  			"1200000000000000000000000",
   461  			uint32(10000),
   462  			false,
   463  			[]byte("payload"),
   464  			1000000,
   465  			1000000,
   466  			big.NewInt(unit.Qev),
   467  			false,
   468  			nil,
   469  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   470  		},
   471  		// act.OwnerAddress() is nil,existing owner, but selfstake is not 0
   472  		{
   473  			1201000,
   474  			identityset.Address(30),
   475  			1,
   476  			"test2",
   477  			identityset.Address(28).String(),
   478  			identityset.Address(10).String(),
   479  			"",
   480  			"1200000000000000000000000",
   481  			"1200000000000000000000000",
   482  			uint32(10000),
   483  			false,
   484  			[]byte("payload"),
   485  			1000000,
   486  			1000000,
   487  			big.NewInt(unit.Qev),
   488  			false,
   489  			nil,
   490  			iotextypes.ReceiptStatus_ErrCandidateAlreadyExist,
   491  		},
   492  		// act.OwnerAddress() is nil,existing candidate, collide with existing name,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist
   493  		// act.OwnerAddress() is nil,existing candidate, collide with existing operator,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist
   494  		// act.OwnerAddress() is nil,new candidate, collide with existing name
   495  		{
   496  			1201000,
   497  			identityset.Address(21),
   498  			1,
   499  			"test",
   500  			identityset.Address(28).String(),
   501  			identityset.Address(10).String(),
   502  			"",
   503  			"1200000000000000000000000",
   504  			"1200000000000000000000000",
   505  			uint32(10000),
   506  			false,
   507  			[]byte("payload"),
   508  			1000000,
   509  			1000000,
   510  			big.NewInt(unit.Qev),
   511  			false,
   512  			nil,
   513  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   514  		},
   515  		// act.OwnerAddress() is nil,new candidate, collide with existing operator
   516  		{
   517  			1201000,
   518  			identityset.Address(21),
   519  			2,
   520  			"test2",
   521  			identityset.Address(28).String(),
   522  			identityset.Address(10).String(),
   523  			"",
   524  			"1200000000000000000000000",
   525  			"1200000000000000000000000",
   526  			uint32(10000),
   527  			false,
   528  			[]byte("payload"),
   529  			1000000,
   530  			1000000,
   531  			big.NewInt(unit.Qev),
   532  			false,
   533  			nil,
   534  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   535  		},
   536  		// register without self-stake
   537  		{
   538  			1201000,
   539  			identityset.Address(27),
   540  			1,
   541  			"test",
   542  			identityset.Address(28).String(),
   543  			identityset.Address(29).String(),
   544  			identityset.Address(30).String(),
   545  			"0",
   546  			"0",
   547  			uint32(10000),
   548  			false,
   549  			nil,
   550  			uint64(1000000),
   551  			uint64(1000000),
   552  			big.NewInt(1),
   553  			true,
   554  			nil,
   555  			iotextypes.ReceiptStatus_Success,
   556  		},
   557  	}
   558  
   559  	for _, test := range tests {
   560  		if test.newProtocol {
   561  			sm, p, _, _ = initAll(t, ctrl)
   562  			csr = newCandidateStateReader(sm)
   563  		}
   564  		require.NoError(setupAccount(sm, test.caller, test.initBalance))
   565  		act, err := action.NewCandidateRegister(test.nonce, test.name, test.operatorAddrStr, test.rewardAddrStr, test.ownerAddrStr, test.amountStr, test.duration, test.autoStake, test.payload, test.gasLimit, test.gasPrice)
   566  		require.NoError(err)
   567  		IntrinsicGas, _ := act.IntrinsicGas()
   568  		ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{
   569  			Caller:       test.caller,
   570  			GasPrice:     test.gasPrice,
   571  			IntrinsicGas: IntrinsicGas,
   572  			Nonce:        test.nonce,
   573  		})
   574  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   575  			BlockHeight:    1,
   576  			BlockTimeStamp: time.Now(),
   577  			GasLimit:       test.blkGasLimit,
   578  		})
   579  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
   580  		g.TsunamiBlockHeight = 0
   581  		ctx = genesis.WithGenesisContext(ctx, g)
   582  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
   583  		require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm)))
   584  		if test.err != nil {
   585  			continue
   586  		}
   587  		r, err := p.Handle(ctx, act, sm)
   588  		require.NoError(err)
   589  		if r != nil {
   590  			require.Equal(uint64(test.status), r.Status)
   591  		} else {
   592  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
   593  		}
   594  
   595  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
   596  			// check the special create bucket and candidate register log
   597  			tLogs := r.TransactionLogs()
   598  			if test.amountStr == "0" {
   599  				require.Equal(1, len(tLogs))
   600  				cLog := tLogs[0]
   601  				require.Equal(test.caller.String(), cLog.Sender)
   602  				require.Equal(address.RewardingPoolAddr, cLog.Recipient)
   603  				require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String())
   604  			} else {
   605  				require.Equal(2, len(tLogs))
   606  				cLog := tLogs[0]
   607  				require.Equal(test.caller.String(), cLog.Sender)
   608  				require.Equal(address.StakingBucketPoolAddr, cLog.Recipient)
   609  				require.Equal(test.amountStr, cLog.Amount.String())
   610  
   611  				cLog = tLogs[1]
   612  				require.Equal(test.caller.String(), cLog.Sender)
   613  				require.Equal(address.RewardingPoolAddr, cLog.Recipient)
   614  				require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String())
   615  			}
   616  
   617  			// test candidate
   618  			candidate, _, err := csr.getCandidate(act.OwnerAddress())
   619  			if act.OwnerAddress() == nil {
   620  				require.Nil(candidate)
   621  				require.Equal(ErrNilParameters, errors.Cause(err))
   622  				candidate, _, err = csr.getCandidate(test.caller)
   623  				require.NoError(err)
   624  				require.Equal(test.caller.String(), candidate.Owner.String())
   625  			} else {
   626  				require.NotNil(candidate)
   627  				require.NoError(err)
   628  				require.Equal(test.ownerAddrStr, candidate.Owner.String())
   629  			}
   630  			require.Equal(test.votesStr, candidate.Votes.String())
   631  			csm, err := NewCandidateStateManager(sm, false)
   632  			require.NoError(err)
   633  			candidate = csm.GetByOwner(candidate.Owner)
   634  			require.NotNil(candidate)
   635  			require.Equal(test.votesStr, candidate.Votes.String())
   636  			require.Equal(test.name, candidate.Name)
   637  			require.Equal(test.operatorAddrStr, candidate.Operator.String())
   638  			require.Equal(test.rewardAddrStr, candidate.Reward.String())
   639  			require.Equal(test.amountStr, candidate.SelfStake.String())
   640  
   641  			// test staker's account
   642  			caller, err := accountutil.LoadAccount(sm, test.caller)
   643  			require.NoError(err)
   644  			actCost, err := act.Cost()
   645  			require.NoError(err)
   646  			total := big.NewInt(0)
   647  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, p.config.RegistrationConsts.Fee))
   648  			require.Equal(test.nonce+1, caller.PendingNonce())
   649  		}
   650  	}
   651  }
   652  
   653  func TestProtocol_HandleCandidateUpdate(t *testing.T) {
   654  	require := require.New(t)
   655  	ctrl := gomock.NewController(t)
   656  	tests := []struct {
   657  		initBalance     int64
   658  		caller          address.Address
   659  		nonce           uint64
   660  		name            string
   661  		operatorAddrStr string
   662  		rewardAddrStr   string
   663  		ownerAddrStr    string
   664  		amountStr       string
   665  		afterUpdate     string
   666  		duration        uint32
   667  		autoStake       bool
   668  		payload         []byte
   669  		gasLimit        uint64
   670  		blkGasLimit     uint64
   671  		gasPrice        *big.Int
   672  		newProtocol     bool
   673  		// candidate update
   674  		updateName     string
   675  		updateOperator string
   676  		updateReward   string
   677  		err            error
   678  		status         iotextypes.ReceiptStatus
   679  	}{
   680  		// fetchCaller ErrNotEnoughBalance
   681  		{
   682  			1200101,
   683  			identityset.Address(27),
   684  			1,
   685  			"test",
   686  			identityset.Address(28).String(),
   687  			identityset.Address(29).String(),
   688  			identityset.Address(27).String(),
   689  			"1200000999999999989300000",
   690  			"",
   691  			uint32(10000),
   692  			false,
   693  			[]byte("payload"),
   694  			uint64(1000000),
   695  			uint64(1000000),
   696  			big.NewInt(1000),
   697  			true,
   698  			"update",
   699  			identityset.Address(31).String(),
   700  			identityset.Address(32).String(),
   701  			nil,
   702  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   703  		},
   704  		// only owner can update candidate
   705  		{
   706  			1000,
   707  			identityset.Address(27),
   708  			1,
   709  			"test",
   710  			identityset.Address(28).String(),
   711  			identityset.Address(29).String(),
   712  			identityset.Address(30).String(),
   713  			"1200000000000000000000000",
   714  			"",
   715  			uint32(10000),
   716  			false,
   717  			[]byte("payload"),
   718  			uint64(1000000),
   719  			uint64(1000000),
   720  			big.NewInt(1000),
   721  			true,
   722  			"update",
   723  			identityset.Address(31).String(),
   724  			identityset.Address(32).String(),
   725  			nil,
   726  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
   727  		},
   728  		// success,update name,operator,reward all empty
   729  		{
   730  			1201000,
   731  			identityset.Address(27),
   732  			1,
   733  			"test",
   734  			identityset.Address(28).String(),
   735  			identityset.Address(29).String(),
   736  			"",
   737  			"1200000000000000000000000",
   738  			"1806204150552640363969204",
   739  			uint32(10000),
   740  			false,
   741  			[]byte("payload"),
   742  			uint64(1000000),
   743  			uint64(1000000),
   744  			big.NewInt(1000),
   745  			true,
   746  			"",
   747  			"",
   748  			"",
   749  			nil,
   750  			iotextypes.ReceiptStatus_Success,
   751  		},
   752  		// upsert,collision
   753  		{
   754  			1201000,
   755  			identityset.Address(27),
   756  			1,
   757  			"test",
   758  			identityset.Address(29).String(),
   759  			identityset.Address(30).String(),
   760  			"",
   761  			"1200000000000000000000000",
   762  			"",
   763  			uint32(10000),
   764  			false,
   765  			[]byte("payload"),
   766  			uint64(1000000),
   767  			uint64(1000000),
   768  			big.NewInt(1000),
   769  			true,
   770  			"test2",
   771  			"",
   772  			"",
   773  			nil,
   774  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   775  		},
   776  		// invalid candidate name
   777  		{
   778  			1201000,
   779  			identityset.Address(27),
   780  			1,
   781  			"test",
   782  			identityset.Address(27).String(),
   783  			identityset.Address(29).String(),
   784  			identityset.Address(27).String(),
   785  			"1200000000000000000000000",
   786  			"1806204150552640363969204",
   787  			uint32(10000),
   788  			false,
   789  			[]byte("payload"),
   790  			uint64(1000000),
   791  			uint64(1000000),
   792  			big.NewInt(1000),
   793  			true,
   794  			"!invalidname",
   795  			identityset.Address(31).String(),
   796  			identityset.Address(32).String(),
   797  			action.ErrInvalidCanName,
   798  			iotextypes.ReceiptStatus_Failure,
   799  		},
   800  		// success,update name, operator and reward address
   801  		{
   802  			1201000,
   803  			identityset.Address(27),
   804  			1,
   805  			"test",
   806  			identityset.Address(27).String(),
   807  			identityset.Address(29).String(),
   808  			identityset.Address(27).String(),
   809  			"1200000000000000000000000",
   810  			"1806204150552640363969204",
   811  			uint32(10000),
   812  			false,
   813  			[]byte("payload"),
   814  			uint64(1000000),
   815  			uint64(1000000),
   816  			big.NewInt(1000),
   817  			true,
   818  			"update",
   819  			identityset.Address(31).String(),
   820  			identityset.Address(32).String(),
   821  			nil,
   822  			iotextypes.ReceiptStatus_Success,
   823  		},
   824  		// upsert,collide with candidate name
   825  		{
   826  			1201000,
   827  			identityset.Address(27),
   828  			uint64(1),
   829  			"test",
   830  			identityset.Address(28).String(),
   831  			identityset.Address(29).String(),
   832  			identityset.Address(27).String(),
   833  			"1200000000000000000000000",
   834  			"",
   835  			uint32(10000),
   836  			true,
   837  			[]byte("payload"),
   838  			uint64(1000000),
   839  			uint64(1000000),
   840  			big.NewInt(1000),
   841  			false,
   842  			"test1",
   843  			"",
   844  			"",
   845  			nil,
   846  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   847  		},
   848  		// upsert,collide with operator address
   849  		{
   850  			1201000,
   851  			identityset.Address(27),
   852  			uint64(1),
   853  			"test",
   854  			identityset.Address(28).String(),
   855  			identityset.Address(29).String(),
   856  			identityset.Address(27).String(),
   857  			"1200000000000000000000000",
   858  			"",
   859  			uint32(10000),
   860  			true,
   861  			[]byte("payload"),
   862  			uint64(1000000),
   863  			uint64(1000000),
   864  			big.NewInt(1000),
   865  			false,
   866  			"test",
   867  			identityset.Address(7).String(),
   868  			"",
   869  			nil,
   870  			iotextypes.ReceiptStatus_ErrCandidateConflict,
   871  		},
   872  	}
   873  
   874  	for _, test := range tests {
   875  		sm, p, _, _ := initAll(t, ctrl)
   876  		csr := newCandidateStateReader(sm)
   877  		require.NoError(setupAccount(sm, test.caller, test.initBalance))
   878  		act, err := action.NewCandidateRegister(test.nonce, test.name, test.operatorAddrStr, test.rewardAddrStr, test.ownerAddrStr, test.amountStr, test.duration, test.autoStake, test.payload, test.gasLimit, test.gasPrice)
   879  		require.NoError(err)
   880  		intrinsic, _ := act.IntrinsicGas()
   881  		ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{
   882  			Caller:       test.caller,
   883  			GasPrice:     test.gasPrice,
   884  			IntrinsicGas: intrinsic,
   885  			Nonce:        test.nonce,
   886  		})
   887  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   888  			BlockHeight:    1,
   889  			BlockTimeStamp: time.Now(),
   890  			GasLimit:       test.blkGasLimit,
   891  		})
   892  		ctx = genesis.WithGenesisContext(ctx, genesis.Default)
   893  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
   894  		_, err = p.Handle(ctx, act, sm)
   895  		require.NoError(err)
   896  
   897  		cu, err := action.NewCandidateUpdate(test.nonce+1, test.updateName, test.updateOperator, test.updateReward, test.gasLimit, test.gasPrice)
   898  		require.NoError(err)
   899  		intrinsic, _ = cu.IntrinsicGas()
   900  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
   901  			Caller:       test.caller,
   902  			GasPrice:     test.gasPrice,
   903  			IntrinsicGas: intrinsic,
   904  			Nonce:        test.nonce + 1,
   905  		})
   906  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   907  			BlockHeight:    1,
   908  			BlockTimeStamp: time.Now(),
   909  			GasLimit:       test.blkGasLimit,
   910  		})
   911  		require.Equal(test.err, errors.Cause(p.Validate(ctx, cu, sm)))
   912  		if test.err != nil {
   913  			continue
   914  		}
   915  		r, err := p.Handle(ctx, cu, sm)
   916  		require.NoError(err)
   917  		if r != nil {
   918  			require.Equal(uint64(test.status), r.Status)
   919  		} else {
   920  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
   921  		}
   922  
   923  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
   924  			// test candidate
   925  			candidate, _, err := csr.getCandidate(act.OwnerAddress())
   926  			if act.OwnerAddress() == nil {
   927  				require.Nil(candidate)
   928  				require.Equal(ErrNilParameters, errors.Cause(err))
   929  				candidate, _, err = csr.getCandidate(test.caller)
   930  				require.NoError(err)
   931  				require.Equal(test.caller.String(), candidate.Owner.String())
   932  			} else {
   933  				require.NotNil(candidate)
   934  				require.NoError(err)
   935  				require.Equal(test.ownerAddrStr, candidate.Owner.String())
   936  			}
   937  			require.Equal(test.afterUpdate, candidate.Votes.String())
   938  			csm, err := NewCandidateStateManager(sm, false)
   939  			require.NoError(err)
   940  			candidate = csm.GetByOwner(candidate.Owner)
   941  			require.NotNil(candidate)
   942  			require.Equal(test.afterUpdate, candidate.Votes.String())
   943  			if test.updateName != "" {
   944  				require.Equal(test.updateName, candidate.Name)
   945  			}
   946  			if test.updateOperator != "" {
   947  				require.Equal(test.updateOperator, candidate.Operator.String())
   948  			}
   949  			if test.updateOperator != "" {
   950  				require.Equal(test.updateReward, candidate.Reward.String())
   951  			}
   952  			require.Equal(test.afterUpdate, candidate.Votes.String())
   953  			require.Equal(test.amountStr, candidate.SelfStake.String())
   954  
   955  			// test staker's account
   956  			caller, err := accountutil.LoadAccount(sm, test.caller)
   957  			require.NoError(err)
   958  			actCost, err := act.Cost()
   959  			require.NoError(err)
   960  			cuCost, err := cu.Cost()
   961  			require.NoError(err)
   962  			total := big.NewInt(0)
   963  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, cuCost).Add(total, p.config.RegistrationConsts.Fee))
   964  			require.Equal(test.nonce+2, caller.PendingNonce())
   965  		}
   966  	}
   967  }
   968  
   969  func TestProtocol_HandleUnstake(t *testing.T) {
   970  	require := require.New(t)
   971  	ctrl := gomock.NewController(t)
   972  
   973  	sm, p, candidate, candidate2 := initAll(t, ctrl)
   974  	csr := newCandidateStateReader(sm)
   975  	initCreateStake(t, sm, identityset.Address(2), 100, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, "100000000000000000000", false)
   976  
   977  	callerAddr := identityset.Address(1)
   978  	tests := []struct {
   979  		// creat stake fields
   980  		caller       address.Address
   981  		amount       string
   982  		autoStake    bool
   983  		afterUnstake string
   984  		initBalance  int64
   985  		selfstaking  bool
   986  		// action fields
   987  		index uint64
   988  		// block context
   989  		blkTimestamp time.Time
   990  		ctxTimestamp time.Time
   991  		// clear flag for inMemCandidates
   992  		clear bool
   993  		// need new p
   994  		newProtocol bool
   995  		err         error
   996  		// expected result
   997  		status iotextypes.ReceiptStatus
   998  	}{
   999  		// fetchCaller ErrNotEnoughBalance
  1000  		{
  1001  			callerAddr,
  1002  			"100990000000000000000",
  1003  			false,
  1004  			"",
  1005  			101,
  1006  			false,
  1007  			0,
  1008  			time.Now(),
  1009  			time.Now(),
  1010  			false,
  1011  			true,
  1012  			nil,
  1013  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  1014  		},
  1015  		// fetchBucket, bucket.Owner is not equal to actionCtx.Caller
  1016  		{
  1017  			identityset.Address(12),
  1018  			"100000000000000000000",
  1019  			false,
  1020  			"",
  1021  			101,
  1022  			false,
  1023  			0,
  1024  			time.Now(),
  1025  			time.Now(),
  1026  			false,
  1027  			false,
  1028  			nil,
  1029  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  1030  		},
  1031  		// fetchBucket,ReceiptStatus_ErrInvalidBucketType cannot happen,because allowSelfStaking is true
  1032  		// fetchBucket and updateBucket call getbucket, ReceiptStatus_ErrInvalidBucketIndex
  1033  		{
  1034  			identityset.Address(33),
  1035  			"100000000000000000000",
  1036  			false,
  1037  			"",
  1038  			101,
  1039  			false,
  1040  			1,
  1041  			time.Now(),
  1042  			time.Now(),
  1043  			false,
  1044  			true,
  1045  			nil,
  1046  			iotextypes.ReceiptStatus_ErrInvalidBucketIndex,
  1047  		},
  1048  		// inMemCandidates.GetByOwner,ErrInvalidOwner
  1049  		{
  1050  			callerAddr,
  1051  			"100000000000000000000",
  1052  			false,
  1053  			"",
  1054  			101,
  1055  			false,
  1056  			0,
  1057  			time.Now(),
  1058  			time.Now(),
  1059  			true,
  1060  			true,
  1061  			nil,
  1062  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
  1063  		},
  1064  		// unstake before maturity
  1065  		{
  1066  			callerAddr,
  1067  			"100000000000000000000",
  1068  			false,
  1069  			"",
  1070  			101,
  1071  			false,
  1072  			0,
  1073  			time.Now(),
  1074  			time.Now(),
  1075  			false,
  1076  			true,
  1077  			nil,
  1078  			iotextypes.ReceiptStatus_ErrUnstakeBeforeMaturity,
  1079  		},
  1080  		// unstake with autoStake bucket
  1081  		{
  1082  			callerAddr,
  1083  			"100000000000000000000",
  1084  			true,
  1085  			"0",
  1086  			101,
  1087  			false,
  1088  			0,
  1089  			time.Now(),
  1090  			time.Now().Add(time.Duration(1) * 24 * time.Hour),
  1091  			false,
  1092  			true,
  1093  			nil,
  1094  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  1095  		},
  1096  		// Upsert error cannot happen,because collision cannot happen
  1097  		// success
  1098  		{
  1099  			callerAddr,
  1100  			"100000000000000000000",
  1101  			false,
  1102  			"0",
  1103  			101,
  1104  			false,
  1105  			0,
  1106  			time.Now(),
  1107  			time.Now().Add(time.Duration(1) * 24 * time.Hour),
  1108  			false,
  1109  			true,
  1110  			nil,
  1111  			iotextypes.ReceiptStatus_Success,
  1112  		},
  1113  	}
  1114  
  1115  	var (
  1116  		ctx       context.Context
  1117  		gasPrice         = big.NewInt(unit.Qev)
  1118  		gasLimit  uint64 = 10000
  1119  		nonce     uint64 = 1
  1120  		blkHeight uint64 = 1
  1121  	)
  1122  
  1123  	for _, test := range tests {
  1124  		if test.newProtocol {
  1125  			sm, p, candidate, _ = initAll(t, ctrl)
  1126  		} else {
  1127  			candidate = candidate2
  1128  		}
  1129  
  1130  		var createCost *big.Int
  1131  		ctx, createCost = initCreateStake(t, sm, test.caller, test.initBalance, big.NewInt(unit.Qev), gasLimit, nonce, blkHeight, test.blkTimestamp, gasLimit, p, candidate, test.amount, test.autoStake)
  1132  		act, err := action.NewUnstake(nonce+1, test.index, nil, gasLimit, gasPrice)
  1133  		require.NoError(err)
  1134  		intrinsicGas, err := act.IntrinsicGas()
  1135  		require.NoError(err)
  1136  		if test.blkTimestamp != test.ctxTimestamp {
  1137  			blkCtx := protocol.MustGetBlockCtx(ctx)
  1138  			blkCtx.BlockTimeStamp = test.ctxTimestamp
  1139  			ctx = protocol.WithBlockCtx(ctx, blkCtx)
  1140  		}
  1141  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1142  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1143  			Caller:       test.caller,
  1144  			GasPrice:     gasPrice,
  1145  			IntrinsicGas: intrinsicGas,
  1146  			Nonce:        nonce + 1,
  1147  		})
  1148  		var r *action.Receipt
  1149  		if test.clear {
  1150  			csm, err := NewCandidateStateManager(sm, false)
  1151  			require.NoError(err)
  1152  			sc, ok := csm.(*candSM)
  1153  			require.True(ok)
  1154  			sc.candCenter.deleteForTestOnly(test.caller)
  1155  			require.False(csm.ContainsOwner(test.caller))
  1156  			r, err = p.handle(ctx, act, csm)
  1157  			require.Equal(test.err, errors.Cause(err))
  1158  		} else {
  1159  			r, err = p.Handle(ctx, act, sm)
  1160  			require.Equal(test.err, errors.Cause(err))
  1161  		}
  1162  		if r != nil {
  1163  			require.Equal(uint64(test.status), r.Status)
  1164  		} else {
  1165  			require.Equal(test.status, iotextypes.ReceiptStatus_Success)
  1166  		}
  1167  
  1168  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
  1169  			// test bucket index and bucket
  1170  			csr = newCandidateStateReader(sm)
  1171  			bucketIndices, _, err := csr.candBucketIndices(candidate.Owner)
  1172  			require.NoError(err)
  1173  			require.Equal(1, len(*bucketIndices))
  1174  			bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner)
  1175  			require.NoError(err)
  1176  			require.Equal(1, len(*bucketIndices))
  1177  			indices := *bucketIndices
  1178  			bucket, err := csr.getBucket(indices[0])
  1179  			require.NoError(err)
  1180  			require.Equal(candidate.Owner.String(), bucket.Candidate.String())
  1181  			require.Equal(test.caller.String(), bucket.Owner.String())
  1182  			require.Equal(test.amount, bucket.StakedAmount.String())
  1183  
  1184  			// test candidate
  1185  			candidate, _, err = csr.getCandidate(candidate.Owner)
  1186  			require.NoError(err)
  1187  			require.Equal(test.afterUnstake, candidate.Votes.String())
  1188  			csm, err := NewCandidateStateManager(sm, false)
  1189  			require.NoError(err)
  1190  			candidate = csm.GetByOwner(candidate.Owner)
  1191  			require.NotNil(candidate)
  1192  			require.Equal(test.afterUnstake, candidate.Votes.String())
  1193  
  1194  			// test staker's account
  1195  			caller, err := accountutil.LoadAccount(sm, test.caller)
  1196  			require.NoError(err)
  1197  			actCost, err := act.Cost()
  1198  			require.NoError(err)
  1199  			require.Equal(nonce+2, caller.PendingNonce())
  1200  			total := big.NewInt(0)
  1201  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
  1202  		}
  1203  	}
  1204  
  1205  	// verify bucket unstaked
  1206  	vb, err := csr.getBucket(0)
  1207  	require.NoError(err)
  1208  	require.True(vb.isUnstaked())
  1209  
  1210  	unstake, err := action.NewUnstake(nonce+2, 0, nil, gasLimit, gasPrice)
  1211  	require.NoError(err)
  1212  	changeCand, err := action.NewChangeCandidate(nonce+2, candidate2.Name, 0, nil, gasLimit, gasPrice)
  1213  	require.NoError(err)
  1214  	deposit, err := action.NewDepositToStake(nonce+2, 0, "10000", nil, gasLimit, gasPrice)
  1215  	require.NoError(err)
  1216  	restake, err := action.NewRestake(nonce+2, 0, 0, false, nil, gasLimit, gasPrice)
  1217  	require.NoError(err)
  1218  
  1219  	unstakedBucketTests := []struct {
  1220  		act       action.Action
  1221  		greenland bool
  1222  		status    iotextypes.ReceiptStatus
  1223  	}{
  1224  		// unstake an already unstaked bucket again not allowed
  1225  		{unstake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
  1226  		// change candidate for an unstaked bucket not allowed
  1227  		{changeCand, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
  1228  		// deposit to unstaked bucket not allowed
  1229  		{deposit, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
  1230  		// restake an unstaked bucket not allowed
  1231  		{restake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType},
  1232  		// restake an unstaked bucket is allowed pre-Greenland
  1233  		{restake, false, iotextypes.ReceiptStatus_ErrNotEnoughBalance},
  1234  	}
  1235  	for i, v := range unstakedBucketTests {
  1236  		greenland := genesis.Default
  1237  		if v.greenland {
  1238  			blkCtx := protocol.MustGetBlockCtx(ctx)
  1239  			greenland.GreenlandBlockHeight = blkCtx.BlockHeight
  1240  		}
  1241  		actCtx := protocol.MustGetActionCtx(ctx)
  1242  		actCtx.Nonce = nonce + 2 + uint64(i)
  1243  		ctx = protocol.WithActionCtx(ctx, actCtx)
  1244  		ctx = genesis.WithGenesisContext(ctx, greenland)
  1245  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1246  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1247  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1248  			Caller:   callerAddr,
  1249  			GasPrice: gasPrice,
  1250  			Nonce:    nonce + 2 + uint64(i),
  1251  		})
  1252  		_, err = p.Start(ctx, sm)
  1253  		require.NoError(err)
  1254  		r, err := p.Handle(ctx, v.act, sm)
  1255  		require.NoError(err)
  1256  		require.EqualValues(v.status, r.Status)
  1257  
  1258  		if !v.greenland {
  1259  			// pre-Greenland allows restaking an unstaked bucket, and it is considered staked afterwards
  1260  			vb, err := csr.getBucket(0)
  1261  			require.NoError(err)
  1262  			require.True(vb.StakeStartTime.Unix() != 0)
  1263  			require.True(vb.UnstakeStartTime.Unix() != 0)
  1264  			require.False(vb.isUnstaked())
  1265  		}
  1266  	}
  1267  }
  1268  
  1269  func TestProtocol_HandleWithdrawStake(t *testing.T) {
  1270  	require := require.New(t)
  1271  	ctrl := gomock.NewController(t)
  1272  	tests := []struct {
  1273  		// create stake fields
  1274  		amount      string
  1275  		initBalance int64
  1276  		selfstaking bool
  1277  		// block context
  1278  		blkTimestamp time.Time
  1279  		ctxTimestamp time.Time
  1280  		// if unstake
  1281  		unstake bool
  1282  		// withdraw fields
  1283  		withdrawIndex uint64
  1284  		// expected result
  1285  		status iotextypes.ReceiptStatus
  1286  	}{
  1287  		// fetchCaller ErrNotEnoughBalance
  1288  		{
  1289  			"100990000000000000000",
  1290  			101,
  1291  			false,
  1292  			time.Now(),
  1293  			time.Now(),
  1294  			true,
  1295  			0,
  1296  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  1297  		},
  1298  		// fetchBucket ReceiptStatus_ErrInvalidBucketIndex
  1299  		{
  1300  			"100000000000000000000",
  1301  			101,
  1302  			false,
  1303  			time.Now(),
  1304  			time.Now(),
  1305  			true,
  1306  			1,
  1307  			iotextypes.ReceiptStatus_ErrInvalidBucketIndex,
  1308  		},
  1309  		// check unstake time,ReceiptStatus_ErrWithdrawBeforeUnstake
  1310  		{
  1311  			"100000000000000000000",
  1312  			101,
  1313  			false,
  1314  			time.Now(),
  1315  			time.Now(),
  1316  			false,
  1317  			0,
  1318  			iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake,
  1319  		},
  1320  		// check ReceiptStatus_ErrWithdrawBeforeMaturity
  1321  		{
  1322  			"100000000000000000000",
  1323  			101,
  1324  			false,
  1325  			time.Now(),
  1326  			time.Now(),
  1327  			true,
  1328  			0,
  1329  			iotextypes.ReceiptStatus_ErrWithdrawBeforeMaturity,
  1330  		},
  1331  		// delxxx cannot happen,because unstake first called without error
  1332  		// ReceiptStatus_Success
  1333  		{
  1334  			"100000000000000000000",
  1335  			101,
  1336  			false,
  1337  			time.Now(),
  1338  			time.Now().Add(time.Hour * 500),
  1339  			true,
  1340  			0,
  1341  			iotextypes.ReceiptStatus_Success,
  1342  		},
  1343  	}
  1344  
  1345  	for _, test := range tests {
  1346  		sm, p, _, candidate := initAll(t, ctrl)
  1347  		csr := newCandidateStateReader(sm)
  1348  		caller := identityset.Address(2)
  1349  		require.NoError(setupAccount(sm, caller, test.initBalance))
  1350  		gasPrice := big.NewInt(unit.Qev)
  1351  		gasLimit := uint64(10000)
  1352  		ctx, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), gasLimit, 1, 1, test.blkTimestamp, gasLimit, p, candidate, test.amount, false)
  1353  		var actCost *big.Int
  1354  		if test.unstake {
  1355  			act, err := action.NewUnstake(1, 0, nil, gasLimit, big.NewInt(unit.Qev))
  1356  			require.NoError(err)
  1357  			intrinsic, err := act.IntrinsicGas()
  1358  			require.NoError(err)
  1359  			actCost, err = act.Cost()
  1360  			require.NoError(err)
  1361  			ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1362  				Caller:       caller,
  1363  				GasPrice:     gasPrice,
  1364  				IntrinsicGas: intrinsic,
  1365  				Nonce:        2,
  1366  			})
  1367  			ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1368  				BlockHeight:    1,
  1369  				BlockTimeStamp: time.Now().Add(time.Duration(1) * 24 * time.Hour),
  1370  				GasLimit:       1000000,
  1371  			})
  1372  
  1373  			_, err = p.Handle(ctx, act, sm)
  1374  			require.NoError(err)
  1375  			require.NoError(p.Commit(ctx, sm))
  1376  			require.Equal(0, sm.Snapshot())
  1377  		}
  1378  
  1379  		withdraw, err := action.NewWithdrawStake(1, test.withdrawIndex,
  1380  			nil, gasLimit, gasPrice)
  1381  		require.NoError(err)
  1382  		actionCtx := protocol.MustGetActionCtx(ctx)
  1383  		blkCtx := protocol.MustGetBlockCtx(ctx)
  1384  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1385  			Caller:       actionCtx.Caller,
  1386  			GasPrice:     actionCtx.GasPrice,
  1387  			IntrinsicGas: actionCtx.IntrinsicGas,
  1388  			Nonce:        actionCtx.Nonce + 1,
  1389  		})
  1390  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1391  			BlockHeight:    blkCtx.BlockHeight,
  1392  			BlockTimeStamp: test.ctxTimestamp,
  1393  			GasLimit:       blkCtx.GasLimit,
  1394  		})
  1395  		r, err := p.Handle(ctx, withdraw, sm)
  1396  		require.NoError(err)
  1397  		if r != nil {
  1398  			require.Equal(uint64(test.status), r.Status)
  1399  		} else {
  1400  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  1401  		}
  1402  
  1403  		if test.status == iotextypes.ReceiptStatus_Success {
  1404  			// check the special withdraw bucket log
  1405  			tLogs := r.TransactionLogs()
  1406  			require.Equal(1, len(tLogs))
  1407  			wLog := tLogs[0]
  1408  			require.Equal(address.StakingBucketPoolAddr, wLog.Sender)
  1409  			require.Equal(caller.String(), wLog.Recipient)
  1410  			require.Equal(test.amount, wLog.Amount.String())
  1411  
  1412  			// test bucket index and bucket
  1413  			_, _, err := csr.candBucketIndices(candidate.Owner)
  1414  			require.Error(err)
  1415  			_, _, err = csr.voterBucketIndices(candidate.Owner)
  1416  			require.Error(err)
  1417  
  1418  			// test staker's account
  1419  			caller, err := accountutil.LoadAccount(sm, caller)
  1420  			require.NoError(err)
  1421  			withdrawCost, err := withdraw.Cost()
  1422  			require.NoError(err)
  1423  			require.Equal(uint64(4), caller.PendingNonce())
  1424  			total := big.NewInt(0)
  1425  			withdrawAmount, ok := new(big.Int).SetString(test.amount, 10)
  1426  			require.True(ok)
  1427  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, withdrawCost).Add(total, createCost).Sub(total, withdrawAmount))
  1428  		}
  1429  	}
  1430  }
  1431  
  1432  func TestProtocol_HandleChangeCandidate(t *testing.T) {
  1433  	require := require.New(t)
  1434  	ctrl := gomock.NewController(t)
  1435  
  1436  	tests := []struct {
  1437  		// creat stake fields
  1438  		caller               address.Address
  1439  		amount               string
  1440  		afterChange          string
  1441  		afterChangeSelfStake string
  1442  		initBalance          int64
  1443  		selfstaking          bool
  1444  		// action fields
  1445  		index         uint64
  1446  		candidateName string
  1447  		gasPrice      *big.Int
  1448  		gasLimit      uint64
  1449  		nonce         uint64
  1450  		// block context
  1451  		blkHeight    uint64
  1452  		blkTimestamp time.Time
  1453  		blkGasLimit  uint64
  1454  		// clear flag for inMemCandidates
  1455  		clear bool
  1456  		// expected result
  1457  		err    error
  1458  		status iotextypes.ReceiptStatus
  1459  	}{
  1460  		// fetchCaller ReceiptStatus_ErrNotEnoughBalance
  1461  		{
  1462  			identityset.Address(1),
  1463  			"100990000000000000000",
  1464  			"0",
  1465  			"0",
  1466  			101,
  1467  			false,
  1468  			1,
  1469  			"test2",
  1470  			big.NewInt(unit.Qev),
  1471  			10000,
  1472  			1,
  1473  			1,
  1474  			time.Now(),
  1475  			10000,
  1476  			true,
  1477  			nil,
  1478  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  1479  		},
  1480  		// ReceiptStatus_ErrCandidateNotExist
  1481  		{
  1482  			identityset.Address(1),
  1483  			"100000000000000000000",
  1484  			"0",
  1485  			"0",
  1486  			101,
  1487  			false,
  1488  			1,
  1489  			"testname",
  1490  			big.NewInt(unit.Qev),
  1491  			10000,
  1492  			1,
  1493  			1,
  1494  			time.Now(),
  1495  			10000,
  1496  			true,
  1497  			nil,
  1498  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
  1499  		},
  1500  		// fetchBucket,ReceiptStatus_ErrInvalidBucketType
  1501  		{
  1502  			identityset.Address(1),
  1503  			"100000000000000000000",
  1504  			"0",
  1505  			"0",
  1506  			101,
  1507  			true,
  1508  			1,
  1509  			"test2",
  1510  			big.NewInt(unit.Qev),
  1511  			10000,
  1512  			1,
  1513  			1,
  1514  			time.Now(),
  1515  			10000,
  1516  			false,
  1517  			nil,
  1518  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  1519  		},
  1520  		// ErrInvalidOwner
  1521  		{
  1522  			identityset.Address(1),
  1523  			"100000000000000000000",
  1524  			"0",
  1525  			"0",
  1526  			101,
  1527  			false,
  1528  			1,
  1529  			"test2",
  1530  			big.NewInt(unit.Qev),
  1531  			10000,
  1532  			1,
  1533  			1,
  1534  			time.Now(),
  1535  			10000,
  1536  			true,
  1537  			nil,
  1538  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
  1539  		},
  1540  		// invalid candidate name
  1541  		{
  1542  			identityset.Address(2),
  1543  			"100000000000000000000",
  1544  			"200000000000000000000",
  1545  			"1200000000000000000000000",
  1546  			101,
  1547  			false,
  1548  			0,
  1549  			"~1",
  1550  			big.NewInt(unit.Qev),
  1551  			10000,
  1552  			1,
  1553  			1,
  1554  			time.Now(),
  1555  			10000,
  1556  			false,
  1557  			action.ErrInvalidCanName,
  1558  			iotextypes.ReceiptStatus_Failure,
  1559  		},
  1560  		// invalid candidate name 2
  1561  		{
  1562  			identityset.Address(2),
  1563  			"100000000000000000000",
  1564  			"200000000000000000000",
  1565  			"1200000000000000000000000",
  1566  			101,
  1567  			false,
  1568  			0,
  1569  			"0123456789abc",
  1570  			big.NewInt(unit.Qev),
  1571  			10000,
  1572  			1,
  1573  			1,
  1574  			time.Now(),
  1575  			10000,
  1576  			false,
  1577  			action.ErrInvalidCanName,
  1578  			iotextypes.ReceiptStatus_Failure,
  1579  		},
  1580  		// invalid candidate name 3
  1581  		{
  1582  			identityset.Address(2),
  1583  			"100000000000000000000",
  1584  			"200000000000000000000",
  1585  			"1200000000000000000000000",
  1586  			101,
  1587  			false,
  1588  			0,
  1589  			"",
  1590  			big.NewInt(unit.Qev),
  1591  			10000,
  1592  			1,
  1593  			1,
  1594  			time.Now(),
  1595  			10000,
  1596  			false,
  1597  			action.ErrInvalidCanName,
  1598  			iotextypes.ReceiptStatus_Failure,
  1599  		},
  1600  		// Upsert error cannot happen,because CreateStake already check collision
  1601  		// change from 0 to test1
  1602  		{
  1603  			identityset.Address(2),
  1604  			"100000000000000000000",
  1605  			"200000000000000000000",
  1606  			"1200000000000000000000000",
  1607  			101,
  1608  			false,
  1609  			0,
  1610  			"test1",
  1611  			big.NewInt(unit.Qev),
  1612  			10000,
  1613  			1,
  1614  			1,
  1615  			time.Now(),
  1616  			10000,
  1617  			false,
  1618  			nil,
  1619  			iotextypes.ReceiptStatus_Success,
  1620  		},
  1621  		// test change to same candidate (Hawaii fix)
  1622  		{
  1623  			identityset.Address(2),
  1624  			"100000000000000000000",
  1625  			"100000000000000000000",
  1626  			"1200000000000000000000000",
  1627  			101,
  1628  			false,
  1629  			0,
  1630  			"test2",
  1631  			big.NewInt(unit.Qev),
  1632  			10000,
  1633  			1,
  1634  			genesis.Default.HawaiiBlockHeight,
  1635  			time.Now(),
  1636  			10000,
  1637  			false,
  1638  			nil,
  1639  			iotextypes.ReceiptStatus_ErrCandidateAlreadyExist,
  1640  		},
  1641  	}
  1642  
  1643  	for _, test := range tests {
  1644  		sm, p, candidate, candidate2 := initAll(t, ctrl)
  1645  		csr := newCandidateStateReader(sm)
  1646  		// candidate2 vote self,index 0
  1647  		ctx, _ := initCreateStake(t, sm, candidate2.Owner, 101, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, "100000000000000000000", false)
  1648  		// candidate vote self,index 1
  1649  		_, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), test.gasLimit, test.nonce, test.blkHeight, test.blkTimestamp, test.blkGasLimit, p, candidate, test.amount, false)
  1650  
  1651  		act, err := action.NewChangeCandidate(test.nonce+1, test.candidateName, test.index, nil, test.gasLimit, test.gasPrice)
  1652  		require.NoError(err)
  1653  		intrinsic, err := act.IntrinsicGas()
  1654  		require.NoError(err)
  1655  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1656  			Caller:       test.caller,
  1657  			GasPrice:     test.gasPrice,
  1658  			IntrinsicGas: intrinsic,
  1659  			Nonce:        test.nonce + 1,
  1660  		})
  1661  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1662  			BlockHeight:    test.blkHeight,
  1663  			BlockTimeStamp: time.Now(),
  1664  			GasLimit:       1000000,
  1665  		})
  1666  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1667  		var r *action.Receipt
  1668  		if test.clear {
  1669  			csm, err := NewCandidateStateManager(sm, false)
  1670  			require.NoError(err)
  1671  			sc, ok := csm.(*candSM)
  1672  			require.True(ok)
  1673  			cc := sc.candCenter.GetBySelfStakingIndex(test.index)
  1674  			sc.candCenter.deleteForTestOnly(cc.Owner)
  1675  			require.False(csm.ContainsOwner(cc.Owner))
  1676  			r, err = p.handle(ctx, act, csm)
  1677  			require.Equal(test.err, errors.Cause(err))
  1678  		} else {
  1679  			require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm)))
  1680  			if test.err != nil {
  1681  				continue
  1682  			}
  1683  			r, err = p.Handle(ctx, act, sm)
  1684  			require.NoError(err)
  1685  		}
  1686  		if r != nil {
  1687  			require.Equal(uint64(test.status), r.Status)
  1688  		} else {
  1689  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  1690  		}
  1691  
  1692  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
  1693  			// test bucket index and bucket
  1694  			bucketIndices, _, err := csr.candBucketIndices(identityset.Address(1))
  1695  			require.NoError(err)
  1696  			require.Equal(2, len(*bucketIndices))
  1697  			bucketIndices, _, err = csr.voterBucketIndices(identityset.Address(1))
  1698  			require.NoError(err)
  1699  			require.Equal(1, len(*bucketIndices))
  1700  			indices := *bucketIndices
  1701  			bucket, err := csr.getBucket(indices[0])
  1702  			require.NoError(err)
  1703  			require.Equal(identityset.Address(1).String(), bucket.Candidate.String())
  1704  			require.Equal(identityset.Address(1).String(), bucket.Owner.String())
  1705  			require.Equal(test.amount, bucket.StakedAmount.String())
  1706  
  1707  			// test candidate
  1708  			candidate, _, err := csr.getCandidate(candidate.Owner)
  1709  			require.NotNil(candidate)
  1710  			require.NoError(err)
  1711  			require.Equal(test.afterChange, candidate.Votes.String())
  1712  			require.Equal(test.candidateName, candidate.Name)
  1713  			require.Equal(candidate.Operator.String(), candidate.Operator.String())
  1714  			require.Equal(candidate.Reward.String(), candidate.Reward.String())
  1715  			require.Equal(candidate.Owner.String(), candidate.Owner.String())
  1716  			require.Equal(test.afterChangeSelfStake, candidate.SelfStake.String())
  1717  			csm, err := NewCandidateStateManager(sm, false)
  1718  			require.NoError(err)
  1719  			candidate = csm.GetByOwner(candidate.Owner)
  1720  			require.NotNil(candidate)
  1721  			require.Equal(test.afterChange, candidate.Votes.String())
  1722  
  1723  			// test staker's account
  1724  			caller, err := accountutil.LoadAccount(sm, test.caller)
  1725  			require.NoError(err)
  1726  			actCost, err := act.Cost()
  1727  			require.NoError(err)
  1728  			require.Equal(test.nonce+2, caller.PendingNonce())
  1729  			total := big.NewInt(0)
  1730  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
  1731  		}
  1732  	}
  1733  }
  1734  
  1735  func TestProtocol_HandleChangeCandidate_ClearPrevCandidateSelfStake(t *testing.T) {
  1736  	r := require.New(t)
  1737  	ctrl := gomock.NewController(t)
  1738  	t.Run("clear if bucket is an expired endorse bucket", func(t *testing.T) {
  1739  		sm, p, buckets, _ := initTestStateWithHeight(t, ctrl,
  1740  			[]*bucketConfig{
  1741  				{identityset.Address(1), identityset.Address(5), "100000000000000000000", 1, true, true, nil, 1},
  1742  			},
  1743  			[]*candidateConfig{
  1744  				{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  1745  				{identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"},
  1746  			}, 1)
  1747  		r.NoError(setupAccount(sm, identityset.Address(5), 10000))
  1748  		nonce := uint64(1)
  1749  		act, err := action.NewChangeCandidate(nonce, "test2", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  1750  		r.NoError(err)
  1751  		intrinsic, err := act.IntrinsicGas()
  1752  		r.NoError(err)
  1753  		ctx := context.Background()
  1754  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  1755  		g.TsunamiBlockHeight = 0
  1756  		ctx = genesis.WithGenesisContext(ctx, g)
  1757  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1758  			Caller:       identityset.Address(5),
  1759  			GasPrice:     big.NewInt(unit.Qev),
  1760  			IntrinsicGas: intrinsic,
  1761  			Nonce:        nonce,
  1762  		})
  1763  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1764  			BlockHeight:    2,
  1765  			BlockTimeStamp: time.Now(),
  1766  			GasLimit:       1000000,
  1767  		})
  1768  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1769  		recipt, err := p.Handle(ctx, act, sm)
  1770  		r.NoError(err)
  1771  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  1772  		// test previous candidate self stake
  1773  		csm, err := NewCandidateStateManager(sm, false)
  1774  		r.NoError(err)
  1775  		prevCand := csm.GetByOwner(identityset.Address(1))
  1776  		r.Equal("0", prevCand.SelfStake.String())
  1777  		r.EqualValues(uint64(candidateNoSelfStakeBucketIndex), prevCand.SelfStakeBucketIdx)
  1778  		r.Equal("0", prevCand.Votes.String())
  1779  	})
  1780  	t.Run("not clear if bucket is a vote bucket", func(t *testing.T) {
  1781  		sm, p, buckets, _ := initTestStateWithHeight(t, ctrl,
  1782  			[]*bucketConfig{
  1783  				{identityset.Address(1), identityset.Address(1), "120000000000000000000", 1, true, true, nil, 0},
  1784  				{identityset.Address(2), identityset.Address(2), "120000000000000000000", 1, true, true, nil, 0},
  1785  				{identityset.Address(1), identityset.Address(1), "100000000000000000000", 1, true, false, nil, 0},
  1786  			},
  1787  			[]*candidateConfig{
  1788  				{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  1789  				{identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"},
  1790  			}, 1)
  1791  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  1792  		nonce := uint64(1)
  1793  		act, err := action.NewChangeCandidate(nonce, "test2", buckets[2].Index, nil, 10000, big.NewInt(unit.Qev))
  1794  		r.NoError(err)
  1795  		intrinsic, err := act.IntrinsicGas()
  1796  		r.NoError(err)
  1797  		ctx := context.Background()
  1798  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  1799  		g.TsunamiBlockHeight = 0
  1800  		ctx = genesis.WithGenesisContext(ctx, g)
  1801  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1802  			Caller:       identityset.Address(1),
  1803  			GasPrice:     big.NewInt(unit.Qev),
  1804  			IntrinsicGas: intrinsic,
  1805  			Nonce:        nonce,
  1806  		})
  1807  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1808  			BlockHeight:    2,
  1809  			BlockTimeStamp: time.Now(),
  1810  			GasLimit:       1000000,
  1811  		})
  1812  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1813  		recipt, err := p.Handle(ctx, act, sm)
  1814  		r.NoError(err)
  1815  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  1816  		// test previous candidate self stake
  1817  		csm, err := NewCandidateStateManager(sm, false)
  1818  		r.NoError(err)
  1819  		prevCand := csm.GetByOwner(buckets[2].Candidate)
  1820  		r.Equal("120000000000000000000", prevCand.SelfStake.String())
  1821  		r.EqualValues(0, prevCand.SelfStakeBucketIdx)
  1822  		r.Equal("124562140820308711042", prevCand.Votes.String())
  1823  	})
  1824  	t.Run("not clear if bucket is a vote bucket", func(t *testing.T) {
  1825  		sm, p, buckets, _ := initTestStateWithHeight(t, ctrl,
  1826  			[]*bucketConfig{
  1827  				{identityset.Address(1), identityset.Address(1), "120000000000000000000", 1, true, true, nil, 0},
  1828  				{identityset.Address(2), identityset.Address(2), "120000000000000000000", 1, true, true, nil, 0},
  1829  				{identityset.Address(1), identityset.Address(1), "100000000000000000000", 1, true, false, nil, 0},
  1830  			},
  1831  			[]*candidateConfig{
  1832  				{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  1833  				{identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"},
  1834  			}, 1)
  1835  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  1836  		nonce := uint64(1)
  1837  		act, err := action.NewChangeCandidate(nonce, "test2", buckets[2].Index, nil, 10000, big.NewInt(unit.Qev))
  1838  		r.NoError(err)
  1839  		intrinsic, err := act.IntrinsicGas()
  1840  		r.NoError(err)
  1841  		ctx := context.Background()
  1842  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  1843  		g.TsunamiBlockHeight = 0
  1844  		ctx = genesis.WithGenesisContext(ctx, g)
  1845  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  1846  			Caller:       identityset.Address(1),
  1847  			GasPrice:     big.NewInt(unit.Qev),
  1848  			IntrinsicGas: intrinsic,
  1849  			Nonce:        nonce,
  1850  		})
  1851  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  1852  			BlockHeight:    2,
  1853  			BlockTimeStamp: time.Now(),
  1854  			GasLimit:       1000000,
  1855  		})
  1856  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  1857  		recipt, err := p.Handle(ctx, act, sm)
  1858  		r.NoError(err)
  1859  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  1860  		// test previous candidate self stake
  1861  		csm, err := NewCandidateStateManager(sm, false)
  1862  		r.NoError(err)
  1863  		prevCand := csm.GetByOwner(buckets[2].Candidate)
  1864  		r.Equal("120000000000000000000", prevCand.SelfStake.String())
  1865  		r.EqualValues(0, prevCand.SelfStakeBucketIdx)
  1866  		r.Equal("124562140820308711042", prevCand.Votes.String())
  1867  	})
  1868  }
  1869  
  1870  func TestProtocol_HandleTransferStake(t *testing.T) {
  1871  	require := require.New(t)
  1872  	ctrl := gomock.NewController(t)
  1873  
  1874  	tests := []struct {
  1875  		// creat stake fields
  1876  		caller        address.Address
  1877  		amount        string
  1878  		afterTransfer uint64
  1879  		initBalance   int64
  1880  		// action fields
  1881  		index    uint64
  1882  		gasPrice *big.Int
  1883  		gasLimit uint64
  1884  		nonce    uint64
  1885  		// block context
  1886  		blkHeight    uint64
  1887  		blkTimestamp time.Time
  1888  		blkGasLimit  uint64
  1889  		// NewTransferStake fields
  1890  		to            address.Address
  1891  		toInitBalance uint64
  1892  		init          bool
  1893  		// expected result
  1894  		err    error
  1895  		status iotextypes.ReceiptStatus
  1896  	}{
  1897  		// fetchCaller ReceiptStatus_ErrNotEnoughBalance
  1898  		{
  1899  			identityset.Address(2),
  1900  			"100990000000000000000",
  1901  			0,
  1902  			101,
  1903  			0,
  1904  			big.NewInt(unit.Qev),
  1905  			1000000000,
  1906  			2,
  1907  			1,
  1908  			time.Now(),
  1909  			10000,
  1910  			identityset.Address(1),
  1911  			1,
  1912  			false,
  1913  			nil,
  1914  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  1915  		},
  1916  		// fetchBucket,bucket.Owner not equal to actionCtx.Caller
  1917  		{
  1918  			identityset.Address(1),
  1919  			"100000000000000000000",
  1920  			0,
  1921  			1000,
  1922  			0,
  1923  			big.NewInt(unit.Qev),
  1924  			10000,
  1925  			1,
  1926  			1,
  1927  			time.Now(),
  1928  			10000,
  1929  			identityset.Address(2),
  1930  			1,
  1931  			true,
  1932  			nil,
  1933  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  1934  		},
  1935  		// fetchBucket,inMemCandidates.ContainsSelfStakingBucket is false
  1936  		{
  1937  			identityset.Address(1),
  1938  			"100000000000000000000",
  1939  			0,
  1940  			101,
  1941  			1,
  1942  			big.NewInt(unit.Qev),
  1943  			10000,
  1944  			1,
  1945  			1,
  1946  			time.Now(),
  1947  			10000,
  1948  			identityset.Address(2),
  1949  			1,
  1950  			true,
  1951  			nil,
  1952  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  1953  		},
  1954  		{
  1955  			identityset.Address(2),
  1956  			"100000000000000000000",
  1957  			0,
  1958  			101,
  1959  			0,
  1960  			big.NewInt(unit.Qev),
  1961  			10000,
  1962  			2,
  1963  			1,
  1964  			time.Now(),
  1965  			10000,
  1966  			identityset.Address(1),
  1967  			1,
  1968  			false,
  1969  			nil,
  1970  			iotextypes.ReceiptStatus_Success,
  1971  		},
  1972  		// test transfer to same owner
  1973  		{
  1974  			identityset.Address(2),
  1975  			"100000000000000000000",
  1976  			0,
  1977  			101,
  1978  			0,
  1979  			big.NewInt(unit.Qev),
  1980  			10000,
  1981  			2,
  1982  			genesis.Default.HawaiiBlockHeight,
  1983  			time.Now(),
  1984  			10000,
  1985  			identityset.Address(2),
  1986  			1,
  1987  			false,
  1988  			nil,
  1989  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  1990  		},
  1991  	}
  1992  
  1993  	for _, test := range tests {
  1994  		sm, p, candi, candidate2 := initAll(t, ctrl)
  1995  		csr := newCandidateStateReader(sm)
  1996  		nonce := uint64(1)
  1997  		if test.caller.String() == candidate2.Owner.String() {
  1998  			nonce++
  1999  		}
  2000  		ctx, createCost := initCreateStake(t, sm, candidate2.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, test.amount, false)
  2001  		if test.init {
  2002  			initCreateStake(t, sm, candi.Owner, test.initBalance, test.gasPrice, test.gasLimit, test.nonce, test.blkHeight, test.blkTimestamp, test.blkGasLimit, p, candi, test.amount, false)
  2003  			if test.caller.String() == candi.Owner.String() {
  2004  				nonce++
  2005  			}
  2006  		} else {
  2007  			require.NoError(setupAccount(sm, identityset.Address(1), 1))
  2008  		}
  2009  
  2010  		act, err := action.NewTransferStake(nonce, test.to.String(), test.index, nil, test.gasLimit, test.gasPrice)
  2011  		require.NoError(err)
  2012  		intrinsic, err := act.IntrinsicGas()
  2013  		require.NoError(err)
  2014  
  2015  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2016  			Caller:       test.caller,
  2017  			GasPrice:     test.gasPrice,
  2018  			IntrinsicGas: intrinsic,
  2019  			Nonce:        nonce,
  2020  		})
  2021  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2022  			BlockHeight:    test.blkHeight,
  2023  			BlockTimeStamp: time.Now(),
  2024  			GasLimit:       10000000,
  2025  		})
  2026  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2027  		r, err := p.Handle(ctx, act, sm)
  2028  		require.Equal(test.err, errors.Cause(err))
  2029  		if r != nil {
  2030  			require.Equal(uint64(test.status), r.Status)
  2031  		} else {
  2032  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  2033  		}
  2034  
  2035  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
  2036  			// test bucket index and bucket
  2037  			bucketIndices, _, err := csr.candBucketIndices(candidate2.Owner)
  2038  			require.NoError(err)
  2039  			require.Equal(1, len(*bucketIndices))
  2040  			bucketIndices, _, err = csr.voterBucketIndices(test.to)
  2041  			require.NoError(err)
  2042  			require.Equal(1, len(*bucketIndices))
  2043  			indices := *bucketIndices
  2044  			bucket, err := csr.getBucket(indices[0])
  2045  			require.NoError(err)
  2046  			require.Equal(candidate2.Owner, bucket.Candidate)
  2047  			require.Equal(test.to.String(), bucket.Owner.String())
  2048  			require.Equal(test.amount, bucket.StakedAmount.String())
  2049  
  2050  			// test candidate
  2051  			candidate, _, err := csr.getCandidate(candi.Owner)
  2052  			require.NoError(err)
  2053  			require.Equal(test.afterTransfer, candidate.Votes.Uint64())
  2054  			csm, err := NewCandidateStateManager(sm, false)
  2055  			require.NoError(err)
  2056  			candidate = csm.GetByOwner(candi.Owner)
  2057  			require.NotNil(candidate)
  2058  			require.LessOrEqual(test.afterTransfer, candidate.Votes.Uint64())
  2059  			require.Equal(candi.Name, candidate.Name)
  2060  			require.Equal(candi.Operator, candidate.Operator)
  2061  			require.Equal(candi.Reward, candidate.Reward)
  2062  			require.Equal(candi.Owner, candidate.Owner)
  2063  			require.Equal(test.afterTransfer, candidate.Votes.Uint64())
  2064  			require.LessOrEqual(test.afterTransfer, candidate.SelfStake.Uint64())
  2065  
  2066  			// test staker's account
  2067  			caller, err := accountutil.LoadAccount(sm, test.caller)
  2068  			require.NoError(err)
  2069  			actCost, err := act.Cost()
  2070  			require.NoError(err)
  2071  			require.Equal(test.nonce+1, caller.PendingNonce())
  2072  			total := big.NewInt(0)
  2073  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
  2074  		}
  2075  	}
  2076  }
  2077  
  2078  func TestProtocol_HandleConsignmentTransfer(t *testing.T) {
  2079  	require := require.New(t)
  2080  	ctrl := gomock.NewController(t)
  2081  
  2082  	tests := []struct {
  2083  		bucketOwner string
  2084  		blkHeight   uint64
  2085  		to          address.Address
  2086  		// consignment fields
  2087  		nilPayload  bool
  2088  		consignType string
  2089  		reclaim     string
  2090  		wrongSig    bool
  2091  		sigIndex    uint64
  2092  		sigNonce    uint64
  2093  		status      iotextypes.ReceiptStatus
  2094  	}{
  2095  		// case I: p.hu.IsPreGreenland(blkCtx.BlockHeight)
  2096  		{
  2097  			identityset.PrivateKey(2).HexString(),
  2098  			1,
  2099  			identityset.Address(3),
  2100  			false,
  2101  			"Ethereum",
  2102  			_reclaim,
  2103  			false,
  2104  			0,
  2105  			1,
  2106  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2107  		},
  2108  		// case II: len(act.Payload()) == 0
  2109  		{
  2110  			identityset.PrivateKey(2).HexString(),
  2111  			5553821,
  2112  			identityset.Address(3),
  2113  			true,
  2114  			"Ethereum",
  2115  			_reclaim,
  2116  			false,
  2117  			0,
  2118  			1,
  2119  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2120  		},
  2121  		// case III: type is not Ethereum
  2122  		{
  2123  			identityset.PrivateKey(2).HexString(),
  2124  			5553821,
  2125  			identityset.Address(3),
  2126  			false,
  2127  			"xx",
  2128  			_reclaim,
  2129  			false,
  2130  			0,
  2131  			1,
  2132  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2133  		},
  2134  		// case IV: msg.Reclaim != _reclaim
  2135  		{
  2136  			identityset.PrivateKey(2).HexString(),
  2137  			5553821,
  2138  			identityset.Address(3),
  2139  			false,
  2140  			"Ethereum",
  2141  			"wrong reclaim",
  2142  			false,
  2143  			0,
  2144  			1,
  2145  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2146  		},
  2147  		// case V: RecoverPubkeyFromEccSig error
  2148  		{
  2149  			identityset.PrivateKey(2).HexString(),
  2150  			5553821,
  2151  			identityset.Address(3),
  2152  			false,
  2153  			"Ethereum",
  2154  			_reclaim,
  2155  			true,
  2156  			0,
  2157  			1,
  2158  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2159  		},
  2160  		// case VI: transferor is not bucket.Owner
  2161  		{
  2162  			identityset.PrivateKey(31).HexString(),
  2163  			5553821,
  2164  			identityset.Address(1),
  2165  			false,
  2166  			"Ethereum",
  2167  			_reclaim,
  2168  			false,
  2169  			0,
  2170  			1,
  2171  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2172  		},
  2173  		// case VII: transferee is not actCtx.Caller
  2174  		{
  2175  			identityset.PrivateKey(32).HexString(),
  2176  			5553821,
  2177  			identityset.Address(3),
  2178  			false,
  2179  			"Ethereum",
  2180  			_reclaim,
  2181  			false,
  2182  			0,
  2183  			1,
  2184  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2185  		},
  2186  		// case VIII: signed asset id is not equal to bucket.Index
  2187  		{
  2188  			identityset.PrivateKey(32).HexString(),
  2189  			5553821,
  2190  			identityset.Address(1),
  2191  			false,
  2192  			"Ethereum",
  2193  			_reclaim,
  2194  			false,
  2195  			1,
  2196  			1,
  2197  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2198  		},
  2199  		// case IX: transfereeNonce is not equal to actCtx.Nonce
  2200  		{
  2201  			identityset.PrivateKey(32).HexString(),
  2202  			5553821,
  2203  			identityset.Address(1),
  2204  			false,
  2205  			"Ethereum",
  2206  			_reclaim,
  2207  			false,
  2208  			0,
  2209  			2,
  2210  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2211  		},
  2212  		// case X: success
  2213  		{
  2214  			identityset.PrivateKey(32).HexString(),
  2215  			6544441,
  2216  			identityset.Address(1),
  2217  			false,
  2218  			"Ethereum",
  2219  			_reclaim,
  2220  			false,
  2221  			0,
  2222  			1,
  2223  			iotextypes.ReceiptStatus_Success,
  2224  		},
  2225  	}
  2226  	for _, test := range tests {
  2227  		sm, p, cand1, cand2 := initAll(t, ctrl)
  2228  		csr := newCandidateStateReader(sm)
  2229  		caller := identityset.Address(1)
  2230  		initBalance := int64(1000)
  2231  		require.NoError(setupAccount(sm, caller, initBalance))
  2232  		stakeAmount := "100000000000000000000"
  2233  		gasPrice := big.NewInt(unit.Qev)
  2234  		gasLimit := uint64(10000)
  2235  		ctx, _ := initCreateStake(t, sm, identityset.Address(32), initBalance, gasPrice, gasLimit, 1, test.blkHeight, time.Now(), gasLimit, p, cand2, stakeAmount, false)
  2236  		initCreateStake(t, sm, identityset.Address(31), initBalance, gasPrice, gasLimit, 1, test.blkHeight, time.Now(), gasLimit, p, cand1, stakeAmount, false)
  2237  
  2238  		// transfer to test.to through consignment
  2239  		var consign []byte
  2240  		if !test.nilPayload {
  2241  			consign = newconsignment(require, test.sigIndex, test.sigNonce, test.bucketOwner, test.to.String(), test.consignType, test.reclaim, test.wrongSig)
  2242  		}
  2243  
  2244  		act, err := action.NewTransferStake(1, caller.String(), 0, consign, gasLimit, gasPrice)
  2245  		require.NoError(err)
  2246  		intrinsic, err := act.IntrinsicGas()
  2247  		require.NoError(err)
  2248  
  2249  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2250  			Caller:       caller,
  2251  			GasPrice:     gasPrice,
  2252  			IntrinsicGas: intrinsic,
  2253  			Nonce:        1,
  2254  		})
  2255  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2256  			BlockHeight:    test.blkHeight,
  2257  			BlockTimeStamp: time.Now(),
  2258  			GasLimit:       gasLimit,
  2259  		})
  2260  		r, err := p.Handle(ctx, act, sm)
  2261  		require.NoError(err)
  2262  		if r != nil {
  2263  			require.Equal(uint64(test.status), r.Status)
  2264  		} else {
  2265  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  2266  		}
  2267  
  2268  		if test.status == iotextypes.ReceiptStatus_Success {
  2269  			// test bucket index and bucket
  2270  			bucketIndices, _, err := csr.candBucketIndices(cand2.Owner)
  2271  			require.NoError(err)
  2272  			require.Equal(1, len(*bucketIndices))
  2273  			bucketIndices, _, err = csr.voterBucketIndices(test.to)
  2274  			require.NoError(err)
  2275  			require.Equal(1, len(*bucketIndices))
  2276  			indices := *bucketIndices
  2277  			bucket, err := csr.getBucket(indices[0])
  2278  			require.NoError(err)
  2279  			require.Equal(cand2.Owner, bucket.Candidate)
  2280  			require.Equal(test.to.String(), bucket.Owner.String())
  2281  			require.Equal(stakeAmount, bucket.StakedAmount.String())
  2282  
  2283  			// test candidate
  2284  			candidate, _, err := csr.getCandidate(cand1.Owner)
  2285  			require.NoError(err)
  2286  			require.LessOrEqual(uint64(0), candidate.Votes.Uint64())
  2287  			csm, err := NewCandidateStateManager(sm, false)
  2288  			require.NoError(err)
  2289  			candidate = csm.GetByOwner(cand1.Owner)
  2290  			require.NotNil(candidate)
  2291  			require.LessOrEqual(uint64(0), candidate.Votes.Uint64())
  2292  			require.Equal(cand1.Name, candidate.Name)
  2293  			require.Equal(cand1.Operator, candidate.Operator)
  2294  			require.Equal(cand1.Reward, candidate.Reward)
  2295  			require.Equal(cand1.Owner, candidate.Owner)
  2296  			require.LessOrEqual(uint64(0), candidate.Votes.Uint64())
  2297  			require.LessOrEqual(uint64(0), candidate.SelfStake.Uint64())
  2298  
  2299  			// test staker's account
  2300  			caller, err := accountutil.LoadAccount(sm, caller)
  2301  			require.NoError(err)
  2302  			actCost, err := act.Cost()
  2303  			require.NoError(err)
  2304  			require.Equal(uint64(2), caller.PendingNonce())
  2305  			total := big.NewInt(0)
  2306  			require.Equal(unit.ConvertIotxToRau(initBalance), total.Add(total, caller.Balance).Add(total, actCost))
  2307  		}
  2308  	}
  2309  }
  2310  
  2311  func TestProtocol_HandleRestake(t *testing.T) {
  2312  	require := require.New(t)
  2313  	ctrl := gomock.NewController(t)
  2314  	callerAddr := identityset.Address(2)
  2315  
  2316  	tests := []struct {
  2317  		// creat stake fields
  2318  		caller       address.Address
  2319  		amount       string
  2320  		afterRestake string
  2321  		initBalance  int64
  2322  		selfstaking  bool
  2323  		// action fields
  2324  		index    uint64
  2325  		gasPrice *big.Int
  2326  		gasLimit uint64
  2327  		nonce    uint64
  2328  		// block context
  2329  		blkHeight    uint64
  2330  		blkTimestamp time.Time
  2331  		blkGasLimit  uint64
  2332  		// restake fields
  2333  		duration  uint32
  2334  		autoStake bool
  2335  		// clear flag for inMemCandidates
  2336  		clear bool
  2337  		// need new p
  2338  		newAccount bool
  2339  		// expected result
  2340  		err    error
  2341  		status iotextypes.ReceiptStatus
  2342  	}{
  2343  		// fetchCaller ReceiptStatus_ErrNotEnoughBalance
  2344  		{
  2345  			callerAddr,
  2346  			"100990000000000000000",
  2347  			"0",
  2348  			101,
  2349  			false,
  2350  			0,
  2351  			big.NewInt(unit.Qev),
  2352  			10000,
  2353  			1,
  2354  			1,
  2355  			time.Now(),
  2356  			10000,
  2357  			1,
  2358  			true,
  2359  			false,
  2360  			false,
  2361  			nil,
  2362  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  2363  		},
  2364  		// fetchBucket, bucket.Owner is not equal to actionCtx.Caller
  2365  		{
  2366  			identityset.Address(12),
  2367  			"100000000000000000000",
  2368  			"0",
  2369  			101,
  2370  			false,
  2371  			0,
  2372  			big.NewInt(unit.Qev),
  2373  			10000,
  2374  			1,
  2375  			1,
  2376  			time.Now(),
  2377  			10000,
  2378  			1,
  2379  			true,
  2380  			false,
  2381  			true,
  2382  			nil,
  2383  			iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
  2384  		},
  2385  		// updateBucket getbucket ErrStateNotExist
  2386  		{
  2387  			identityset.Address(33),
  2388  			"100000000000000000000",
  2389  			"0",
  2390  			101,
  2391  			false,
  2392  			1,
  2393  			big.NewInt(unit.Qev),
  2394  			10000,
  2395  			1,
  2396  			1,
  2397  			time.Now(),
  2398  			10000,
  2399  			1,
  2400  			true,
  2401  			false,
  2402  			true,
  2403  			nil,
  2404  			iotextypes.ReceiptStatus_ErrInvalidBucketIndex,
  2405  		},
  2406  		// for inMemCandidates.GetByOwner,ErrInvalidOwner
  2407  		{
  2408  			callerAddr,
  2409  			"100000000000000000000",
  2410  			"0",
  2411  			101,
  2412  			false,
  2413  			0,
  2414  			big.NewInt(unit.Qev),
  2415  			10000,
  2416  			1,
  2417  			1,
  2418  			time.Now(),
  2419  			10000,
  2420  			1,
  2421  			true,
  2422  			true,
  2423  			false,
  2424  			nil,
  2425  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
  2426  		},
  2427  		// autoStake = true, set up duration
  2428  		{
  2429  			callerAddr,
  2430  			"100000000000000000000",
  2431  			"103801784016923925869",
  2432  			101,
  2433  			false,
  2434  			0,
  2435  			big.NewInt(unit.Qev),
  2436  			10000,
  2437  			1,
  2438  			1,
  2439  			time.Now(),
  2440  			10000,
  2441  			0,
  2442  			true,
  2443  			false,
  2444  			false,
  2445  			nil,
  2446  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  2447  		},
  2448  		// autoStake = false, set up duration
  2449  		{
  2450  			callerAddr,
  2451  			"100000000000000000000",
  2452  			"103801784016923925869",
  2453  			101,
  2454  			false,
  2455  			0,
  2456  			big.NewInt(unit.Qev),
  2457  			10000,
  2458  			1,
  2459  			1,
  2460  			time.Now(),
  2461  			10000,
  2462  			0,
  2463  			false,
  2464  			false,
  2465  			false,
  2466  			nil,
  2467  			iotextypes.ReceiptStatus_ErrReduceDurationBeforeMaturity,
  2468  		},
  2469  		// candidate.SubVote,ErrInvalidAmount cannot happen,because previous vote with no error
  2470  		// ReceiptStatus_Success
  2471  		{
  2472  			callerAddr,
  2473  			"100000000000000000000",
  2474  			"103801784016923925869",
  2475  			101,
  2476  			false,
  2477  			0,
  2478  			big.NewInt(unit.Qev),
  2479  			10000,
  2480  			1,
  2481  			1,
  2482  			time.Now(),
  2483  			10000,
  2484  			1,
  2485  			true,
  2486  			false,
  2487  			false,
  2488  			nil,
  2489  			iotextypes.ReceiptStatus_Success,
  2490  		},
  2491  	}
  2492  
  2493  	for _, test := range tests {
  2494  		sm, p, candidate, candidate2 := initAll(t, ctrl)
  2495  		csr := newCandidateStateReader(sm)
  2496  		ctx, createCost := initCreateStake(t, sm, candidate2.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, test.nonce, 1, time.Now(), 10000, p, candidate2, test.amount, test.autoStake)
  2497  
  2498  		if test.newAccount {
  2499  			require.NoError(setupAccount(sm, test.caller, test.initBalance))
  2500  		} else {
  2501  			candidate = candidate2
  2502  		}
  2503  		nonce := test.nonce
  2504  		if test.caller.String() == candidate2.Owner.String() {
  2505  			nonce++
  2506  		}
  2507  
  2508  		act, err := action.NewRestake(nonce, test.index, test.duration, test.autoStake, nil, test.gasLimit, test.gasPrice)
  2509  		require.NoError(err)
  2510  		intrinsic, err := act.IntrinsicGas()
  2511  		require.NoError(err)
  2512  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2513  			Caller:       test.caller,
  2514  			GasPrice:     test.gasPrice,
  2515  			IntrinsicGas: intrinsic,
  2516  			Nonce:        nonce,
  2517  		})
  2518  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2519  			BlockHeight:    1,
  2520  			BlockTimeStamp: time.Now(),
  2521  			GasLimit:       10000000,
  2522  		})
  2523  		var r *action.Receipt
  2524  		if test.clear {
  2525  			csm, err := NewCandidateStateManager(sm, false)
  2526  			require.NoError(err)
  2527  			sc, ok := csm.(*candSM)
  2528  			require.True(ok)
  2529  			sc.candCenter.deleteForTestOnly(test.caller)
  2530  			require.False(csm.ContainsOwner(test.caller))
  2531  			ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2532  			r, err = p.handle(ctx, act, csm)
  2533  			require.Equal(test.err, errors.Cause(err))
  2534  		} else {
  2535  			r, err = p.Handle(ctx, act, sm)
  2536  			require.Equal(test.err, errors.Cause(err))
  2537  		}
  2538  		if r != nil {
  2539  			require.Equal(uint64(test.status), r.Status)
  2540  		} else {
  2541  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  2542  		}
  2543  
  2544  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
  2545  			// test bucket index and bucket
  2546  			bucketIndices, _, err := csr.candBucketIndices(candidate.Owner)
  2547  			require.NoError(err)
  2548  			require.Equal(1, len(*bucketIndices))
  2549  			bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner)
  2550  			require.NoError(err)
  2551  			require.Equal(1, len(*bucketIndices))
  2552  			indices := *bucketIndices
  2553  			bucket, err := csr.getBucket(indices[0])
  2554  			require.NoError(err)
  2555  			require.Equal(candidate.Owner.String(), bucket.Candidate.String())
  2556  			require.Equal(test.caller.String(), bucket.Owner.String())
  2557  			require.Equal(test.amount, bucket.StakedAmount.String())
  2558  
  2559  			// test candidate
  2560  			candidate, _, err = csr.getCandidate(candidate.Owner)
  2561  			require.NoError(err)
  2562  			require.Equal(test.afterRestake, candidate.Votes.String())
  2563  			csm, err := NewCandidateStateManager(sm, false)
  2564  			require.NoError(err)
  2565  			candidate = csm.GetByOwner(candidate.Owner)
  2566  			require.NotNil(candidate)
  2567  			require.Equal(test.afterRestake, candidate.Votes.String())
  2568  
  2569  			// test staker's account
  2570  			caller, err := accountutil.LoadAccount(sm, test.caller)
  2571  			require.NoError(err)
  2572  			actCost, err := act.Cost()
  2573  			require.NoError(err)
  2574  			require.Equal(test.nonce+2, caller.PendingNonce())
  2575  			total := big.NewInt(0)
  2576  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
  2577  		}
  2578  	}
  2579  }
  2580  
  2581  func TestProtocol_HandleDepositToStake(t *testing.T) {
  2582  	require := require.New(t)
  2583  	ctrl := gomock.NewController(t)
  2584  	tests := []struct {
  2585  		// creat stake fields
  2586  		caller       address.Address
  2587  		amount       string
  2588  		afterDeposit string
  2589  		initBalance  int64
  2590  		selfstaking  bool
  2591  		// action fields
  2592  		index    uint64
  2593  		gasPrice *big.Int
  2594  		gasLimit uint64
  2595  		nonce    uint64
  2596  		// block context
  2597  		blkHeight    uint64
  2598  		blkTimestamp time.Time
  2599  		blkGasLimit  uint64
  2600  		autoStake    bool
  2601  		// clear flag for inMemCandidates
  2602  		clear bool
  2603  		// need new p
  2604  		newAccount bool
  2605  		// expected result
  2606  		err    error
  2607  		status iotextypes.ReceiptStatus
  2608  	}{
  2609  		// fetchCaller ErrNotEnoughBalance
  2610  		{
  2611  			identityset.Address(1),
  2612  			"100000000000000000000",
  2613  			"0",
  2614  			10,
  2615  			false,
  2616  			0,
  2617  			big.NewInt(unit.Qev),
  2618  			10000,
  2619  			2,
  2620  			1,
  2621  			time.Now(),
  2622  			10000,
  2623  			false,
  2624  			false,
  2625  			false,
  2626  			nil,
  2627  			iotextypes.ReceiptStatus_ErrNotEnoughBalance,
  2628  		},
  2629  		// fetchBucket ReceiptStatus_ErrInvalidBucketIndex
  2630  		{
  2631  			identityset.Address(12),
  2632  			"100000000000000000000",
  2633  			"0",
  2634  			101,
  2635  			false,
  2636  			1,
  2637  			big.NewInt(unit.Qev),
  2638  			10000,
  2639  			1,
  2640  			1,
  2641  			time.Now(),
  2642  			10000,
  2643  			false,
  2644  			false,
  2645  			true,
  2646  			nil,
  2647  			iotextypes.ReceiptStatus_ErrInvalidBucketIndex,
  2648  		},
  2649  		// fetchBucket ReceiptStatus_ErrInvalidBucketType
  2650  		{
  2651  			identityset.Address(33),
  2652  			"100000000000000000000",
  2653  			"0",
  2654  			101,
  2655  			false,
  2656  			0,
  2657  			big.NewInt(unit.Qev),
  2658  			10000,
  2659  			1,
  2660  			1,
  2661  			time.Now(),
  2662  			10000,
  2663  			false,
  2664  			true,
  2665  			true,
  2666  			nil,
  2667  			iotextypes.ReceiptStatus_ErrInvalidBucketType,
  2668  		},
  2669  		// for inMemCandidates.GetByOwner,ErrInvalidOwner
  2670  		{
  2671  			identityset.Address(1),
  2672  			"100000000000000000000",
  2673  			"0",
  2674  			201,
  2675  			false,
  2676  			0,
  2677  			big.NewInt(unit.Qev),
  2678  			10000,
  2679  			2,
  2680  			1,
  2681  			time.Now(),
  2682  			10000,
  2683  			true,
  2684  			true,
  2685  			false,
  2686  			nil,
  2687  			iotextypes.ReceiptStatus_ErrCandidateNotExist,
  2688  		},
  2689  		// ReceiptStatus_Success
  2690  		{
  2691  			identityset.Address(1),
  2692  			"100000000000000000000",
  2693  			"207603568033847851737",
  2694  			201,
  2695  			false,
  2696  			0,
  2697  			big.NewInt(unit.Qev),
  2698  			10000,
  2699  			2,
  2700  			1,
  2701  			time.Now(),
  2702  			10000,
  2703  			true,
  2704  			false,
  2705  			false,
  2706  			nil,
  2707  			iotextypes.ReceiptStatus_Success,
  2708  		},
  2709  	}
  2710  
  2711  	for _, test := range tests {
  2712  		sm, p, candidate, _ := initAll(t, ctrl)
  2713  		csr := newCandidateStateReader(sm)
  2714  		ctx, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate, test.amount, test.autoStake)
  2715  
  2716  		if test.newAccount {
  2717  			require.NoError(setupAccount(sm, test.caller, test.initBalance))
  2718  		}
  2719  
  2720  		act, err := action.NewDepositToStake(test.nonce, test.index, test.amount, nil, test.gasLimit, test.gasPrice)
  2721  		require.NoError(err)
  2722  		intrinsic, err := act.IntrinsicGas()
  2723  		require.NoError(err)
  2724  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2725  			Caller:       test.caller,
  2726  			GasPrice:     test.gasPrice,
  2727  			IntrinsicGas: intrinsic,
  2728  			Nonce:        test.nonce,
  2729  		})
  2730  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2731  			BlockHeight:    1,
  2732  			BlockTimeStamp: time.Now(),
  2733  			GasLimit:       10000000,
  2734  		})
  2735  		var r *action.Receipt
  2736  		if test.clear {
  2737  			csm, err := NewCandidateStateManager(sm, false)
  2738  			require.NoError(err)
  2739  			sc, ok := csm.(*candSM)
  2740  			require.True(ok)
  2741  			sc.candCenter.deleteForTestOnly(test.caller)
  2742  			require.False(csm.ContainsOwner(test.caller))
  2743  			ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2744  			r, err = p.handle(ctx, act, csm)
  2745  			require.Equal(test.err, errors.Cause(err))
  2746  		} else {
  2747  			r, err = p.Handle(ctx, act, sm)
  2748  			require.Equal(test.err, errors.Cause(err))
  2749  		}
  2750  		if r != nil {
  2751  			require.Equal(uint64(test.status), r.Status)
  2752  		} else {
  2753  			require.Equal(test.status, iotextypes.ReceiptStatus_Failure)
  2754  		}
  2755  
  2756  		if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
  2757  			// check the special deposit bucket log
  2758  			tLogs := r.TransactionLogs()
  2759  			require.Equal(1, len(tLogs))
  2760  			dLog := tLogs[0]
  2761  			require.Equal(test.caller.String(), dLog.Sender)
  2762  			require.Equal(address.StakingBucketPoolAddr, dLog.Recipient)
  2763  			require.Equal(test.amount, dLog.Amount.String())
  2764  
  2765  			// test bucket index and bucket
  2766  			bucketIndices, _, err := csr.candBucketIndices(candidate.Owner)
  2767  			require.NoError(err)
  2768  			require.Equal(1, len(*bucketIndices))
  2769  			bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner)
  2770  			require.NoError(err)
  2771  			require.Equal(1, len(*bucketIndices))
  2772  			indices := *bucketIndices
  2773  
  2774  			bucket, err := csr.getBucket(indices[0])
  2775  			require.NoError(err)
  2776  			require.Equal(candidate.Owner.String(), bucket.Candidate.String())
  2777  			require.Equal(test.caller.String(), bucket.Owner.String())
  2778  			amount, _ := new(big.Int).SetString(test.amount, 10)
  2779  			require.Zero(new(big.Int).Mul(amount, big.NewInt(2)).Cmp(bucket.StakedAmount))
  2780  
  2781  			// test candidate
  2782  			candidate, _, err = csr.getCandidate(candidate.Owner)
  2783  			require.NoError(err)
  2784  			require.Equal(test.afterDeposit, candidate.Votes.String())
  2785  			csm, err := NewCandidateStateManager(sm, false)
  2786  			require.NoError(err)
  2787  			candidate = csm.GetByOwner(candidate.Owner)
  2788  			require.NotNil(candidate)
  2789  			require.Equal(test.afterDeposit, candidate.Votes.String())
  2790  
  2791  			// test staker's account
  2792  			caller, err := accountutil.LoadAccount(sm, test.caller)
  2793  			require.NoError(err)
  2794  			actCost, err := act.Cost()
  2795  			require.NoError(err)
  2796  			require.Equal(test.nonce+1, caller.PendingNonce())
  2797  			total := big.NewInt(0)
  2798  			require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost))
  2799  		}
  2800  	}
  2801  }
  2802  
  2803  func TestProtocol_FetchBucketAndValidate(t *testing.T) {
  2804  	require := require.New(t)
  2805  	ctrl := gomock.NewController(t)
  2806  	sm, p, _, _ := initAll(t, ctrl)
  2807  
  2808  	t.Run("bucket not exist", func(t *testing.T) {
  2809  		csm, err := NewCandidateStateManager(sm, false)
  2810  		require.NoError(err)
  2811  		patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) {
  2812  			return nil, state.ErrStateNotExist
  2813  		})
  2814  		defer patches.Reset()
  2815  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, true)
  2816  		require.ErrorContains(err, "failed to fetch bucket")
  2817  	})
  2818  	t.Run("validate owner", func(t *testing.T) {
  2819  		csm, err := NewCandidateStateManager(sm, false)
  2820  		require.NoError(err)
  2821  		patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) {
  2822  			return &VoteBucket{
  2823  				Owner: identityset.Address(1),
  2824  			}, nil
  2825  		})
  2826  		defer patches.Reset()
  2827  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(2), 1, true, true)
  2828  		require.ErrorContains(err, "bucket owner does not match")
  2829  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, true)
  2830  		require.NoError(err)
  2831  	})
  2832  	t.Run("validate selfstake", func(t *testing.T) {
  2833  		csm, err := NewCandidateStateManager(sm, false)
  2834  		require.NoError(err)
  2835  		patches := gomonkey.NewPatches()
  2836  		defer patches.Reset()
  2837  		patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) {
  2838  			return &VoteBucket{
  2839  				Owner: identityset.Address(1),
  2840  			}, nil
  2841  		})
  2842  		isSelfStake := true
  2843  		isSelfStakeErr := error(nil)
  2844  		patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) {
  2845  			return isSelfStake, isSelfStakeErr
  2846  		})
  2847  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false)
  2848  		require.ErrorContains(err, "self staking bucket cannot be processed")
  2849  		isSelfStake = false
  2850  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false)
  2851  		require.NoError(err)
  2852  		isSelfStakeErr = errors.New("unknown error")
  2853  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false)
  2854  		require.ErrorContains(err, "unknown error")
  2855  	})
  2856  	t.Run("validate owner and selfstake", func(t *testing.T) {
  2857  		csm, err := NewCandidateStateManager(sm, false)
  2858  		require.NoError(err)
  2859  		patches := gomonkey.NewPatches()
  2860  		patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) {
  2861  			return &VoteBucket{
  2862  				Owner: identityset.Address(1),
  2863  			}, nil
  2864  		})
  2865  		patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) {
  2866  			return false, nil
  2867  		})
  2868  		defer patches.Reset()
  2869  
  2870  		_, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, false)
  2871  		require.NoError(err)
  2872  	})
  2873  }
  2874  
  2875  func TestChangeCandidate(t *testing.T) {
  2876  	r := require.New(t)
  2877  	ctrl := gomock.NewController(t)
  2878  	t.Run("Self-Staked as owned", func(t *testing.T) {
  2879  		bucketCfgs := []*bucketConfig{
  2880  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0},
  2881  		}
  2882  		candCfgs := []*candidateConfig{
  2883  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  2884  		}
  2885  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  2886  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  2887  		// csm, err := NewCandidateStateManager(sm, false)
  2888  		nonce := uint64(1)
  2889  		act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  2890  		r.NoError(err)
  2891  		intrinsic, err := act.IntrinsicGas()
  2892  		r.NoError(err)
  2893  		ctx := context.Background()
  2894  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  2895  		g.TsunamiBlockHeight = 0
  2896  		ctx = genesis.WithGenesisContext(ctx, g)
  2897  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2898  			Caller:       identityset.Address(1),
  2899  			GasPrice:     big.NewInt(unit.Qev),
  2900  			IntrinsicGas: intrinsic,
  2901  			Nonce:        nonce,
  2902  		})
  2903  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2904  			BlockHeight:    2,
  2905  			BlockTimeStamp: time.Now(),
  2906  			GasLimit:       1000000,
  2907  		})
  2908  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2909  		recipt, err := p.Handle(ctx, act, sm)
  2910  		r.NoError(err)
  2911  		r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status)
  2912  	})
  2913  	t.Run("Self-Staked with endorsement", func(t *testing.T) {
  2914  		bucketCfgs := []*bucketConfig{
  2915  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0},
  2916  		}
  2917  		candCfgs := []*candidateConfig{
  2918  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  2919  		}
  2920  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  2921  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  2922  		esm := NewEndorsementStateManager(sm)
  2923  		err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: endorsementNotExpireHeight})
  2924  		r.NoError(err)
  2925  		nonce := uint64(1)
  2926  		act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  2927  		r.NoError(err)
  2928  		intrinsic, err := act.IntrinsicGas()
  2929  		r.NoError(err)
  2930  		ctx := context.Background()
  2931  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  2932  		g.TsunamiBlockHeight = 0
  2933  		ctx = genesis.WithGenesisContext(ctx, g)
  2934  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2935  			Caller:       identityset.Address(1),
  2936  			GasPrice:     big.NewInt(unit.Qev),
  2937  			IntrinsicGas: intrinsic,
  2938  			Nonce:        nonce,
  2939  		})
  2940  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2941  			BlockHeight:    2,
  2942  			BlockTimeStamp: time.Now(),
  2943  			GasLimit:       1000000,
  2944  		})
  2945  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2946  		recipt, err := p.Handle(ctx, act, sm)
  2947  		r.NoError(err)
  2948  		r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status)
  2949  	})
  2950  	t.Run("Endorsement is withdrawing", func(t *testing.T) {
  2951  		bucketCfgs := []*bucketConfig{
  2952  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 0},
  2953  		}
  2954  		candCfgs := []*candidateConfig{
  2955  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  2956  		}
  2957  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  2958  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  2959  		esm := NewEndorsementStateManager(sm)
  2960  		err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: 10})
  2961  		r.NoError(err)
  2962  		nonce := uint64(1)
  2963  		act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  2964  		r.NoError(err)
  2965  		intrinsic, err := act.IntrinsicGas()
  2966  		r.NoError(err)
  2967  		ctx := context.Background()
  2968  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  2969  		g.TsunamiBlockHeight = 0
  2970  		ctx = genesis.WithGenesisContext(ctx, g)
  2971  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  2972  			Caller:       identityset.Address(1),
  2973  			GasPrice:     big.NewInt(unit.Qev),
  2974  			IntrinsicGas: intrinsic,
  2975  			Nonce:        nonce,
  2976  		})
  2977  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  2978  			BlockHeight:    2,
  2979  			BlockTimeStamp: time.Now(),
  2980  			GasLimit:       1000000,
  2981  		})
  2982  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  2983  		recipt, err := p.Handle(ctx, act, sm)
  2984  		r.NoError(err)
  2985  		r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status)
  2986  	})
  2987  	t.Run("Endorsement is expired", func(t *testing.T) {
  2988  		bucketCfgs := []*bucketConfig{
  2989  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 1},
  2990  		}
  2991  		candCfgs := []*candidateConfig{
  2992  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  2993  		}
  2994  		sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 2)
  2995  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  2996  		nonce := uint64(1)
  2997  		act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  2998  		r.NoError(err)
  2999  		intrinsic, err := act.IntrinsicGas()
  3000  		r.NoError(err)
  3001  		ctx := context.Background()
  3002  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  3003  		g.TsunamiBlockHeight = 0
  3004  		ctx = genesis.WithGenesisContext(ctx, g)
  3005  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  3006  			Caller:       identityset.Address(1),
  3007  			GasPrice:     big.NewInt(unit.Qev),
  3008  			IntrinsicGas: intrinsic,
  3009  			Nonce:        nonce,
  3010  		})
  3011  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3012  			BlockHeight:    2,
  3013  			BlockTimeStamp: time.Now(),
  3014  			GasLimit:       1000000,
  3015  		})
  3016  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3017  		recipt, err := p.Handle(ctx, act, sm)
  3018  		r.NoError(err)
  3019  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  3020  	})
  3021  }
  3022  
  3023  func TestUnstake(t *testing.T) {
  3024  	r := require.New(t)
  3025  	ctrl := gomock.NewController(t)
  3026  	t.Run("Self-Staked as owned", func(t *testing.T) {
  3027  		bucketCfgs := []*bucketConfig{
  3028  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, true, nil, 0},
  3029  		}
  3030  		candCfgs := []*candidateConfig{
  3031  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  3032  		}
  3033  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  3034  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  3035  		nonce := uint64(1)
  3036  		act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  3037  		r.NoError(err)
  3038  		intrinsic, err := act.IntrinsicGas()
  3039  		r.NoError(err)
  3040  		ctx := context.Background()
  3041  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  3042  		g.TsunamiBlockHeight = 0
  3043  		ctx = genesis.WithGenesisContext(ctx, g)
  3044  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  3045  			Caller:       identityset.Address(1),
  3046  			GasPrice:     big.NewInt(unit.Qev),
  3047  			IntrinsicGas: intrinsic,
  3048  			Nonce:        nonce,
  3049  		})
  3050  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3051  			BlockHeight:    2,
  3052  			BlockTimeStamp: time.Now(),
  3053  			GasLimit:       1000000,
  3054  		})
  3055  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3056  		recipt, err := p.Handle(ctx, act, sm)
  3057  		r.NoError(err)
  3058  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  3059  	})
  3060  	t.Run("Self-Staked with endorsement", func(t *testing.T) {
  3061  		bucketCfgs := []*bucketConfig{
  3062  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, true, nil, 0},
  3063  		}
  3064  		candCfgs := []*candidateConfig{
  3065  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  3066  		}
  3067  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  3068  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  3069  		esm := NewEndorsementStateManager(sm)
  3070  		err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: endorsementNotExpireHeight})
  3071  		r.NoError(err)
  3072  		nonce := uint64(1)
  3073  		act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  3074  		r.NoError(err)
  3075  		intrinsic, err := act.IntrinsicGas()
  3076  		r.NoError(err)
  3077  		ctx := context.Background()
  3078  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  3079  		g.TsunamiBlockHeight = 0
  3080  		ctx = genesis.WithGenesisContext(ctx, g)
  3081  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  3082  			Caller:       identityset.Address(1),
  3083  			GasPrice:     big.NewInt(unit.Qev),
  3084  			IntrinsicGas: intrinsic,
  3085  			Nonce:        nonce,
  3086  		})
  3087  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3088  			BlockHeight:    2,
  3089  			BlockTimeStamp: time.Now(),
  3090  			GasLimit:       1000000,
  3091  		})
  3092  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3093  		recipt, err := p.Handle(ctx, act, sm)
  3094  		r.NoError(err)
  3095  		r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status)
  3096  	})
  3097  	t.Run("Endorsement is withdrawing", func(t *testing.T) {
  3098  		bucketCfgs := []*bucketConfig{
  3099  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 0},
  3100  		}
  3101  		candCfgs := []*candidateConfig{
  3102  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  3103  		}
  3104  		sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs)
  3105  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  3106  		esm := NewEndorsementStateManager(sm)
  3107  		err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: 10})
  3108  		r.NoError(err)
  3109  		nonce := uint64(1)
  3110  		act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  3111  		r.NoError(err)
  3112  		intrinsic, err := act.IntrinsicGas()
  3113  		r.NoError(err)
  3114  		ctx := context.Background()
  3115  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  3116  		g.TsunamiBlockHeight = 0
  3117  		ctx = genesis.WithGenesisContext(ctx, g)
  3118  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  3119  			Caller:       identityset.Address(1),
  3120  			GasPrice:     big.NewInt(unit.Qev),
  3121  			IntrinsicGas: intrinsic,
  3122  			Nonce:        nonce,
  3123  		})
  3124  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3125  			BlockHeight:    2,
  3126  			BlockTimeStamp: time.Now(),
  3127  			GasLimit:       1000000,
  3128  		})
  3129  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3130  		recipt, err := p.Handle(ctx, act, sm)
  3131  		r.NoError(err)
  3132  		r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status)
  3133  	})
  3134  	t.Run("Endorsement is expired", func(t *testing.T) {
  3135  		bucketCfgs := []*bucketConfig{
  3136  			{identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, false, nil, 1},
  3137  		}
  3138  		candCfgs := []*candidateConfig{
  3139  			{identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"},
  3140  		}
  3141  		sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 1)
  3142  		r.NoError(setupAccount(sm, identityset.Address(1), 10000))
  3143  		nonce := uint64(1)
  3144  		act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev))
  3145  		r.NoError(err)
  3146  		intrinsic, err := act.IntrinsicGas()
  3147  		r.NoError(err)
  3148  		ctx := context.Background()
  3149  		g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
  3150  		g.TsunamiBlockHeight = 0
  3151  		ctx = genesis.WithGenesisContext(ctx, g)
  3152  		ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{
  3153  			Caller:       identityset.Address(1),
  3154  			GasPrice:     big.NewInt(unit.Qev),
  3155  			IntrinsicGas: intrinsic,
  3156  			Nonce:        nonce,
  3157  		})
  3158  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3159  			BlockHeight:    2,
  3160  			BlockTimeStamp: time.Now(),
  3161  			GasLimit:       1000000,
  3162  		})
  3163  		ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3164  		recipt, err := p.Handle(ctx, act, sm)
  3165  		r.NoError(err)
  3166  		r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status)
  3167  	})
  3168  }
  3169  
  3170  func initCreateStake(t *testing.T, sm protocol.StateManager, callerAddr address.Address, initBalance int64, gasPrice *big.Int, gasLimit uint64, nonce uint64, blkHeight uint64, blkTimestamp time.Time, blkGasLimit uint64, p *Protocol, candidate *Candidate, amount string, autoStake bool) (context.Context, *big.Int) {
  3171  	require := require.New(t)
  3172  	require.NoError(setupAccount(sm, callerAddr, initBalance))
  3173  	a, err := action.NewCreateStake(nonce, candidate.Name, amount, 1, autoStake,
  3174  		nil, gasLimit, gasPrice)
  3175  	require.NoError(err)
  3176  	intrinsic, err := a.IntrinsicGas()
  3177  	require.NoError(err)
  3178  	ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{
  3179  		Caller:       callerAddr,
  3180  		GasPrice:     gasPrice,
  3181  		IntrinsicGas: intrinsic,
  3182  		Nonce:        nonce,
  3183  	})
  3184  	ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
  3185  		BlockHeight:    blkHeight,
  3186  		BlockTimeStamp: blkTimestamp,
  3187  		GasLimit:       blkGasLimit,
  3188  	})
  3189  	ctx = genesis.WithGenesisContext(ctx, genesis.Default)
  3190  	ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
  3191  	v, err := p.Start(ctx, sm)
  3192  	require.NoError(err)
  3193  	cc, ok := v.(*ViewData)
  3194  	require.True(ok)
  3195  	require.NoError(sm.WriteView(_protocolID, cc))
  3196  	_, err = p.Handle(ctx, a, sm)
  3197  	require.NoError(err)
  3198  	cost, err := a.Cost()
  3199  	require.NoError(err)
  3200  	return ctx, cost
  3201  }
  3202  
  3203  func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Protocol, *Candidate, *Candidate) {
  3204  	require := require.New(t)
  3205  	sm := testdb.NewMockStateManager(ctrl)
  3206  	csm := newCandidateStateManager(sm)
  3207  	_, err := sm.PutState(
  3208  		&totalBucketCount{count: 0},
  3209  		protocol.NamespaceOption(_stakingNameSpace),
  3210  		protocol.KeyOption(TotalBucketKey),
  3211  	)
  3212  	require.NoError(err)
  3213  
  3214  	// create protocol
  3215  	p, err := NewProtocol(depositGas, &BuilderConfig{
  3216  		Staking:                  genesis.Default.Staking,
  3217  		PersistStakingPatchBlock: math.MaxUint64,
  3218  	}, nil, nil, genesis.Default.GreenlandBlockHeight)
  3219  	require.NoError(err)
  3220  
  3221  	// set up candidate
  3222  	candidate := testCandidates[0].d.Clone()
  3223  	candidate.Votes = big.NewInt(0)
  3224  	require.NoError(csm.putCandidate(candidate))
  3225  	candidate2 := testCandidates[1].d.Clone()
  3226  	candidate2.Votes = big.NewInt(0)
  3227  	require.NoError(csm.putCandidate(candidate2))
  3228  	ctx := genesis.WithGenesisContext(context.Background(), genesis.Default)
  3229  	ctx = protocol.WithFeatureWithHeightCtx(ctx)
  3230  	v, err := p.Start(ctx, sm)
  3231  	require.NoError(err)
  3232  	cc, ok := v.(*ViewData)
  3233  	require.True(ok)
  3234  	require.NoError(sm.WriteView(_protocolID, cc))
  3235  	return sm, p, candidate, candidate2
  3236  }
  3237  
  3238  func setupAccount(sm protocol.StateManager, addr address.Address, balance int64) error {
  3239  	if balance < 0 {
  3240  		return errors.New("balance cannot be negative")
  3241  	}
  3242  	account, err := accountutil.LoadOrCreateAccount(sm, addr, state.LegacyNonceAccountTypeOption())
  3243  	if err != nil {
  3244  		return err
  3245  	}
  3246  	if err := account.SubBalance(account.Balance); err != nil {
  3247  		return err
  3248  	}
  3249  	if err := account.AddBalance(unit.ConvertIotxToRau(balance)); err != nil {
  3250  		return err
  3251  	}
  3252  	return accountutil.StoreAccount(sm, addr, account)
  3253  }
  3254  
  3255  func depositGas(ctx context.Context, sm protocol.StateManager, gasFee *big.Int) (*action.TransactionLog, error) {
  3256  	actionCtx := protocol.MustGetActionCtx(ctx)
  3257  	// Subtract balance from caller
  3258  	acc, err := accountutil.LoadAccount(sm, actionCtx.Caller)
  3259  	if err != nil {
  3260  		return nil, err
  3261  	}
  3262  	// TODO: replace with SubBalance, and then change `Balance` to a function
  3263  	acc.Balance = big.NewInt(0).Sub(acc.Balance, gasFee)
  3264  	return nil, accountutil.StoreAccount(sm, actionCtx.Caller, acc)
  3265  }
  3266  
  3267  func newconsignment(r *require.Assertions, bucketIdx, nonce uint64, senderPrivate, recipient, consignTpye, reclaim string, wrongSig bool) []byte {
  3268  	msg := action.ConsignMsgEther{
  3269  		BucketIdx: bucketIdx,
  3270  		Nonce:     nonce,
  3271  		Recipient: recipient,
  3272  		Reclaim:   reclaim,
  3273  	}
  3274  	b, err := json.Marshal(msg)
  3275  	r.NoError(err)
  3276  	h, err := action.MsgHash(consignTpye, b)
  3277  	if err != nil {
  3278  		h = []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  3279  	}
  3280  	sk, err := crypto.HexStringToPrivateKey(senderPrivate)
  3281  	r.NoError(err)
  3282  	sig, err := sk.Sign(h)
  3283  	r.NoError(err)
  3284  	c := &action.ConsignJSON{
  3285  		Type: consignTpye,
  3286  		Msg:  string(b),
  3287  		Sig:  hex.EncodeToString(sig),
  3288  	}
  3289  	if wrongSig {
  3290  		c.Sig = "123456"
  3291  	}
  3292  	b, err = json.Marshal(c)
  3293  	r.NoError(err)
  3294  	return b
  3295  }