github.com/iotexproject/iotex-core@v1.14.1-rc1/e2etest/native_staking_test.go (about)

     1  package e2etest
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/iotexproject/go-pkgs/hash"
    13  	"github.com/iotexproject/iotex-address/address"
    14  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    15  
    16  	"github.com/iotexproject/iotex-core/action"
    17  	"github.com/iotexproject/iotex-core/action/protocol"
    18  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    19  	"github.com/iotexproject/iotex-core/action/protocol/staking"
    20  	"github.com/iotexproject/iotex-core/actpool"
    21  	"github.com/iotexproject/iotex-core/blockchain"
    22  	"github.com/iotexproject/iotex-core/blockchain/block"
    23  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    24  	"github.com/iotexproject/iotex-core/config"
    25  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    26  	"github.com/iotexproject/iotex-core/server/itx"
    27  	"github.com/iotexproject/iotex-core/state"
    28  	"github.com/iotexproject/iotex-core/test/identityset"
    29  	"github.com/iotexproject/iotex-core/testutil"
    30  )
    31  
    32  const (
    33  	// TODO: to be removed
    34  	_const = byte(iota)
    35  	_bucket
    36  	_voterIndex
    37  	_candIndex
    38  	_stakingNameSpace   = "Staking"
    39  	_candidateNameSpace = "Candidate"
    40  
    41  	candidate1Name = "candidate1"
    42  	candidate2Name = "candidate2"
    43  	candidate3Name = "candidate3"
    44  )
    45  
    46  var (
    47  	selfStake, _     = new(big.Int).SetString("1200000000000000000000000", 10)
    48  	cand1Votes, _    = new(big.Int).SetString("1635067133824581908640994", 10)
    49  	vote, _          = new(big.Int).SetString("100000000000000000000", 10)
    50  	autoStakeVote, _ = new(big.Int).SetString("103801784016923925869", 10)
    51  	initBalance, _   = new(big.Int).SetString("100000000000000000000000000", 10)
    52  )
    53  
    54  var (
    55  	gasPrice = big.NewInt(0)
    56  	gasLimit = uint64(1000000)
    57  )
    58  
    59  func TestNativeStaking(t *testing.T) {
    60  	require := require.New(t)
    61  
    62  	testInitCands := []genesis.BootstrapCandidate{
    63  		{
    64  			OwnerAddress:      identityset.Address(22).String(),
    65  			OperatorAddress:   identityset.Address(23).String(),
    66  			RewardAddress:     identityset.Address(23).String(),
    67  			Name:              "test1",
    68  			SelfStakingTokens: selfStake.String(),
    69  		},
    70  		{
    71  			OwnerAddress:      identityset.Address(24).String(),
    72  			OperatorAddress:   identityset.Address(25).String(),
    73  			RewardAddress:     identityset.Address(25).String(),
    74  			Name:              "test2",
    75  			SelfStakingTokens: selfStake.String(),
    76  		},
    77  	}
    78  
    79  	testNativeStaking := func(cfg config.Config, t *testing.T) {
    80  		ctx := context.Background()
    81  
    82  		// Create a new blockchain
    83  		svr, err := itx.NewServer(cfg)
    84  		require.NoError(err)
    85  		require.NoError(svr.Start(ctx))
    86  		defer func() {
    87  			require.NoError(svr.Stop(ctx))
    88  		}()
    89  
    90  		chainID := cfg.Chain.ID
    91  		bc := svr.ChainService(chainID).Blockchain()
    92  		sf := svr.ChainService(chainID).StateFactory()
    93  		ap := svr.ChainService(chainID).ActionPool()
    94  		require.NotNil(bc)
    95  		prtcl, ok := svr.ChainService(chainID).Registry().Find("staking")
    96  		require.True(ok)
    97  		stkPrtcl := prtcl.(*staking.Protocol)
    98  
    99  		require.True(cfg.Genesis.IsFbkMigration(1))
   100  
   101  		// Create two candidates
   102  		cand1Addr := identityset.Address(0)
   103  		cand1PriKey := identityset.PrivateKey(0)
   104  
   105  		cand2Addr := identityset.Address(1)
   106  		cand2PriKey := identityset.PrivateKey(1)
   107  
   108  		// create non-stake candidate
   109  		cand3Addr := identityset.Address(4)
   110  		cand3PriKey := identityset.PrivateKey(4)
   111  
   112  		fixedTime := time.Unix(cfg.Genesis.Timestamp, 0)
   113  		addOneTx := func(tx *action.SealedEnvelope, err error) (*action.SealedEnvelope, *action.Receipt, error) {
   114  			if err != nil {
   115  				return tx, nil, err
   116  			}
   117  			if err := ap.Add(ctx, tx); err != nil {
   118  				return tx, nil, err
   119  			}
   120  			blk, err := createAndCommitBlock(bc, ap, fixedTime)
   121  			if err != nil {
   122  				return tx, nil, err
   123  			}
   124  			h, err := tx.Hash()
   125  			if err != nil {
   126  				return tx, nil, err
   127  			}
   128  			for _, r := range blk.Receipts {
   129  				if r.ActionHash == h {
   130  					return tx, r, nil
   131  				}
   132  			}
   133  			return tx, nil, errors.Errorf("failed to find receipt for %x", h)
   134  		}
   135  
   136  		register1, r1, err := addOneTx(action.SignedCandidateRegister(1, candidate1Name, cand1Addr.String(), cand1Addr.String(),
   137  			cand1Addr.String(), selfStake.String(), 91, true, nil, gasLimit, gasPrice, cand1PriKey))
   138  		require.NoError(err)
   139  		register2, _, err := addOneTx(action.SignedCandidateRegister(1, candidate2Name, cand2Addr.String(), cand2Addr.String(),
   140  			cand2Addr.String(), selfStake.String(), 1, false, nil, gasLimit, gasPrice, cand2PriKey))
   141  		require.NoError(err)
   142  		// check candidate state
   143  		require.NoError(checkCandidateState(sf, candidate1Name, cand1Addr.String(), selfStake, cand1Votes, cand1Addr))
   144  		require.NoError(checkCandidateState(sf, candidate2Name, cand2Addr.String(), selfStake, selfStake, cand2Addr))
   145  
   146  		// check candidate account state
   147  		require.NoError(checkAccountState(cfg, sf, register1, true, initBalance, cand1Addr))
   148  		require.NoError(checkAccountState(cfg, sf, register2, true, initBalance, cand2Addr))
   149  
   150  		// get self-stake index from receipts
   151  		require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status)
   152  		logs := r1.Logs()
   153  		require.Equal(3, len(logs[0].Topics))
   154  		require.Equal(hash.BytesToHash256([]byte(staking.HandleCandidateRegister)), logs[0].Topics[0])
   155  		selfstakeIndex1 := byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:])
   156  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   157  
   158  		// create two stakes from two voters
   159  		voter1Addr := identityset.Address(2)
   160  		voter1PriKey := identityset.PrivateKey(2)
   161  
   162  		voter2Addr := identityset.Address(3)
   163  		voter2PriKey := identityset.PrivateKey(3)
   164  
   165  		cs1, r1, err := addOneTx(action.SignedCreateStake(1, candidate1Name, vote.String(), 1, false,
   166  			nil, gasLimit, gasPrice, voter1PriKey))
   167  		require.NoError(err)
   168  		cs2, r2, err := addOneTx(action.SignedCreateStake(1, candidate1Name, vote.String(), 1, false,
   169  			nil, gasLimit, gasPrice, voter2PriKey))
   170  		require.NoError(err)
   171  
   172  		// check candidate state
   173  		expectedVotes := big.NewInt(0).Add(cand1Votes, big.NewInt(0).Mul(vote, big.NewInt(2)))
   174  		require.NoError(checkCandidateState(sf, candidate1Name, cand1Addr.String(), selfStake, expectedVotes, cand1Addr))
   175  
   176  		// check voter account state
   177  		require.NoError(checkAccountState(cfg, sf, cs1, false, initBalance, voter1Addr))
   178  		require.NoError(checkAccountState(cfg, sf, cs2, false, initBalance, voter2Addr))
   179  
   180  		// get bucket index from receipts
   181  		require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status)
   182  		logs = r1.Logs()
   183  		require.Equal(3, len(logs[0].Topics))
   184  		require.Equal(hash.BytesToHash256([]byte(staking.HandleCreateStake)), logs[0].Topics[0])
   185  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   186  		voter1BucketIndex := byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:])
   187  
   188  		require.EqualValues(iotextypes.ReceiptStatus_Success, r2.Status)
   189  		logs = r2.Logs()
   190  		require.Equal(3, len(logs[0].Topics))
   191  		require.Equal(hash.BytesToHash256([]byte(staking.HandleCreateStake)), logs[0].Topics[0])
   192  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   193  		voter2BucketIndex := byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:])
   194  
   195  		// change candidate
   196  		_, rc, err := addOneTx(action.SignedChangeCandidate(2, candidate2Name, voter2BucketIndex, nil,
   197  			gasLimit, gasPrice, voter2PriKey))
   198  		require.NoError(err)
   199  
   200  		require.EqualValues(iotextypes.ReceiptStatus_Success, rc.Status)
   201  		logs = rc.Logs()
   202  		require.Equal(4, len(logs[0].Topics))
   203  		require.Equal(hash.BytesToHash256([]byte(staking.HandleChangeCandidate)), logs[0].Topics[0])
   204  		require.Equal(voter2BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   205  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   206  		require.Equal(hash.BytesToHash256(cand2Addr.Bytes()), logs[0].Topics[3])
   207  
   208  		// check candidate state
   209  		expectedVotes = big.NewInt(0).Add(cand1Votes, vote)
   210  		require.NoError(checkCandidateState(sf, candidate1Name, cand1Addr.String(), selfStake, expectedVotes, cand1Addr))
   211  		expectedVotes = big.NewInt(0).Add(selfStake, vote)
   212  		require.NoError(checkCandidateState(sf, candidate2Name, cand2Addr.String(), selfStake, expectedVotes, cand2Addr))
   213  
   214  		// transfer stake
   215  		_, rt, err := addOneTx(action.SignedTransferStake(2, voter2Addr.String(), voter1BucketIndex, nil, gasLimit, gasPrice, voter1PriKey))
   216  		require.NoError(err)
   217  
   218  		require.EqualValues(iotextypes.ReceiptStatus_Success, rt.Status)
   219  		logs = rt.Logs()
   220  		require.Equal(4, len(logs[0].Topics))
   221  		require.Equal(hash.BytesToHash256([]byte(staking.HandleTransferStake)), logs[0].Topics[0])
   222  		require.Equal(voter1BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   223  		require.Equal(hash.BytesToHash256(voter2Addr.Bytes()), logs[0].Topics[2])
   224  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[3])
   225  
   226  		// check buckets
   227  		var bis staking.BucketIndices
   228  		_, err = sf.State(&bis, protocol.NamespaceOption(_stakingNameSpace),
   229  			protocol.KeyOption(staking.AddrKeyWithPrefix(voter1Addr, _voterIndex)))
   230  		require.Error(err)
   231  		require.Equal(state.ErrStateNotExist, errors.Cause(err))
   232  
   233  		_, err = sf.State(&bis, protocol.NamespaceOption(_stakingNameSpace),
   234  			protocol.KeyOption(staking.AddrKeyWithPrefix(voter2Addr, _voterIndex)))
   235  		require.NoError(err)
   236  		require.Equal(2, len(bis))
   237  		require.Equal(voter2BucketIndex, bis[0])
   238  		require.Equal(voter1BucketIndex, bis[1])
   239  
   240  		// deposit to stake
   241  		ds, rd, err := addOneTx(action.SignedDepositToStake(3, voter2BucketIndex, vote.String(), nil, gasLimit, gasPrice, voter2PriKey))
   242  		require.NoError(err)
   243  
   244  		require.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, rd.Status)
   245  		logs = rd.Logs()
   246  		require.Equal(4, len(logs[0].Topics))
   247  		require.Equal(hash.BytesToHash256([]byte(staking.HandleDepositToStake)), logs[0].Topics[0])
   248  		require.Equal(voter2BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   249  		require.Equal(hash.BytesToHash256(voter2Addr.Bytes()), logs[0].Topics[2])
   250  		require.Equal(hash.BytesToHash256(cand2Addr.Bytes()), logs[0].Topics[3])
   251  
   252  		// restake
   253  		_, rr, err := addOneTx(action.SignedRestake(4, voter2BucketIndex, 1, true, nil,
   254  			gasLimit, gasPrice, voter2PriKey))
   255  		require.NoError(err)
   256  
   257  		require.EqualValues(iotextypes.ReceiptStatus_Success, rr.Status)
   258  		logs = rr.Logs()
   259  		require.Equal(3, len(logs[0].Topics))
   260  		require.Equal(hash.BytesToHash256([]byte(staking.HandleRestake)), logs[0].Topics[0])
   261  		require.Equal(voter2BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   262  		require.Equal(hash.BytesToHash256(cand2Addr.Bytes()), logs[0].Topics[2])
   263  
   264  		// check candidate state
   265  		expectedVotes = big.NewInt(0).Add(selfStake, autoStakeVote)
   266  		require.NoError(checkCandidateState(sf, candidate2Name, cand2Addr.String(), selfStake, expectedVotes, cand2Addr))
   267  
   268  		// deposit to stake again
   269  		ds, rd, err = addOneTx(action.SignedDepositToStake(5, voter2BucketIndex, vote.String(), nil, gasLimit, gasPrice, voter2PriKey))
   270  		require.NoError(err)
   271  
   272  		// check voter account state
   273  		require.NoError(checkAccountState(cfg, sf, ds, false, big.NewInt(0).Sub(initBalance, vote), voter2Addr))
   274  
   275  		// unstake voter stake
   276  		_, ru, err := addOneTx(action.SignedReclaimStake(false, 6, voter1BucketIndex, nil, gasLimit, gasPrice, voter2PriKey))
   277  		require.NoError(err)
   278  
   279  		require.Equal(uint64(iotextypes.ReceiptStatus_ErrUnstakeBeforeMaturity), ru.Status)
   280  		logs = ru.Logs()
   281  		require.Equal(3, len(logs[0].Topics))
   282  		require.Equal(hash.BytesToHash256([]byte(staking.HandleUnstake)), logs[0].Topics[0])
   283  		require.Equal(voter1BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   284  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   285  
   286  		unstakeTime := fixedTime.Add(time.Duration(1) * 24 * time.Hour)
   287  		addOneTx = func(tx *action.SealedEnvelope, err error) (*action.SealedEnvelope, *action.Receipt, error) {
   288  			if err != nil {
   289  				return tx, nil, err
   290  			}
   291  			if err := ap.Add(ctx, tx); err != nil {
   292  				return tx, nil, err
   293  			}
   294  			blk, err := createAndCommitBlock(bc, ap, unstakeTime)
   295  			if err != nil {
   296  				return tx, nil, err
   297  			}
   298  			h, err := tx.Hash()
   299  			if err != nil {
   300  				return tx, nil, err
   301  			}
   302  			for _, r := range blk.Receipts {
   303  				if r.ActionHash == h {
   304  					return tx, r, nil
   305  				}
   306  			}
   307  			return tx, nil, errors.Errorf("failed to find receipt for %x", h)
   308  		}
   309  
   310  		// unstake with correct timestamp
   311  		_, ru, err = addOneTx(action.SignedReclaimStake(false, 7, voter1BucketIndex, nil, gasLimit, gasPrice, voter2PriKey))
   312  		require.NoError(err)
   313  
   314  		require.Equal(uint64(iotextypes.ReceiptStatus_Success), ru.Status)
   315  		logs = ru.Logs()
   316  		require.Equal(3, len(logs[0].Topics))
   317  		require.Equal(hash.BytesToHash256([]byte(staking.HandleUnstake)), logs[0].Topics[0])
   318  		require.Equal(voter1BucketIndex, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   319  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   320  
   321  		// check candidate state
   322  		require.NoError(checkCandidateState(sf, candidate1Name, cand1Addr.String(), selfStake, cand1Votes, cand1Addr))
   323  
   324  		// unstake self stake
   325  		_, ru, err = addOneTx(action.SignedReclaimStake(false, 2, selfstakeIndex1, nil, gasLimit, gasPrice, cand1PriKey))
   326  		require.NoError(err)
   327  
   328  		require.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, ru.Status)
   329  		logs = ru.Logs()
   330  		require.Equal(3, len(logs[0].Topics))
   331  		require.Equal(hash.BytesToHash256([]byte(staking.HandleUnstake)), logs[0].Topics[0])
   332  		require.Equal(selfstakeIndex1, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   333  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   334  
   335  		// check candidate state
   336  		require.NoError(checkCandidateState(sf, candidate1Name, cand1Addr.String(), selfStake, cand1Votes, cand1Addr))
   337  
   338  		// withdraw stake
   339  		ws, rw, err := addOneTx(action.SignedReclaimStake(true, 3, selfstakeIndex1, nil, gasLimit, gasPrice, cand1PriKey))
   340  		require.NoError(err)
   341  
   342  		require.EqualValues(iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake, rw.Status)
   343  		logs = rw.Logs()
   344  		require.Equal(3, len(logs[0].Topics))
   345  		require.Equal(hash.BytesToHash256([]byte(staking.HandleWithdrawStake)), logs[0].Topics[0])
   346  		require.Equal(selfstakeIndex1, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   347  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   348  
   349  		// withdraw	with correct timestamp
   350  		unstakeTime = unstakeTime.Add(cfg.Genesis.WithdrawWaitingPeriod)
   351  		ws, rw, err = addOneTx(action.SignedReclaimStake(true, 4, selfstakeIndex1, nil, gasLimit, gasPrice, cand1PriKey))
   352  		require.NoError(err)
   353  
   354  		require.EqualValues(iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake, rw.Status)
   355  		logs = rw.Logs()
   356  		require.Equal(3, len(logs[0].Topics))
   357  		require.Equal(hash.BytesToHash256([]byte(staking.HandleWithdrawStake)), logs[0].Topics[0])
   358  		require.Equal(selfstakeIndex1, byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]))
   359  		require.Equal(hash.BytesToHash256(cand1Addr.Bytes()), logs[0].Topics[2])
   360  
   361  		// check buckets
   362  		_, err = sf.State(&bis, protocol.NamespaceOption(_stakingNameSpace),
   363  			protocol.KeyOption(staking.AddrKeyWithPrefix(cand1Addr, _voterIndex)))
   364  		require.NoError(err)
   365  		require.Equal(1, len(bis))
   366  
   367  		_, err = sf.State(&bis, protocol.NamespaceOption(_stakingNameSpace),
   368  			protocol.KeyOption(staking.AddrKeyWithPrefix(cand1Addr, _candIndex)))
   369  		require.NoError(err)
   370  		require.Equal(2, len(bis))
   371  
   372  		// check candidate account state
   373  		require.NoError(checkAccountState(cfg, sf, ws, true, big.NewInt(0).Sub(initBalance, selfStake), cand1Addr))
   374  
   375  		// register without stake
   376  		register3, r3, err := addOneTx(action.SignedCandidateRegister(1, candidate3Name, cand3Addr.String(), cand3Addr.String(),
   377  			cand3Addr.String(), "0", 1, false, nil, gasLimit, gasPrice, cand3PriKey))
   378  		require.NoError(err)
   379  		require.EqualValues(iotextypes.ReceiptStatus_Success, r3.Status)
   380  		require.NoError(checkCandidateState(sf, candidate3Name, cand3Addr.String(), big.NewInt(0), big.NewInt(0), cand3Addr))
   381  		require.NoError(checkAccountState(cfg, sf, register3, true, initBalance, cand3Addr))
   382  
   383  		ctx, err = bc.Context(ctx)
   384  		require.NoError(err)
   385  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   386  			BlockHeight: bc.TipHeight() + 1,
   387  		})
   388  		ctx = protocol.WithFeatureCtx(ctx)
   389  		cands, err := stkPrtcl.ActiveCandidates(ctx, sf, 0)
   390  		require.NoError(err)
   391  		require.Equal(4, len(cands))
   392  		for _, cand := range cands {
   393  			t.Logf("\ncandidate=%+v, %+v\n", string(cand.CanName), cand.Votes.String())
   394  		}
   395  		// stake bucket
   396  		_, cr3, err := addOneTx(action.SignedCreateStake(3, candidate3Name, selfStake.String(), 1, false,
   397  			nil, gasLimit, gasPrice, voter1PriKey))
   398  		require.NoError(err)
   399  		require.EqualValues(iotextypes.ReceiptStatus_Success, cr3.Status)
   400  		logs = cr3.Logs()
   401  		require.Equal(3, len(logs[0].Topics))
   402  		require.Equal(hash.BytesToHash256([]byte(staking.HandleCreateStake)), logs[0].Topics[0])
   403  		endorseBucketIndex := byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:])
   404  		t.Logf("endorseBucketIndex=%+v", endorseBucketIndex)
   405  		// endorse bucket
   406  		_, esr, err := addOneTx(action.SignedCandidateEndorsement(4, endorseBucketIndex, true, gasLimit, gasPrice, voter1PriKey))
   407  		require.NoError(err)
   408  		require.NoError(err)
   409  		require.EqualValues(iotextypes.ReceiptStatus_Success, esr.Status)
   410  		// candidate self stake
   411  		_, cssr, err := addOneTx(action.SignedCandidateActivate(2, endorseBucketIndex, gasLimit, gasPrice, cand3PriKey))
   412  		require.NoError(err)
   413  		require.EqualValues(iotextypes.ReceiptStatus_Success, cssr.Status)
   414  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   415  			BlockHeight: bc.TipHeight() + 1,
   416  		})
   417  		ctx = protocol.WithFeatureCtx(ctx)
   418  		cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0)
   419  		require.NoError(err)
   420  		require.Equal(5, len(cands))
   421  		for _, cand := range cands {
   422  			t.Logf("\ncandidate=%+v, %+v\n", string(cand.CanName), cand.Votes.String())
   423  		}
   424  		// unendorse bucket
   425  		_, esr, err = addOneTx(action.SignedCandidateEndorsement(5, endorseBucketIndex, false, gasLimit, gasPrice, voter1PriKey))
   426  		require.NoError(err)
   427  		require.EqualValues(iotextypes.ReceiptStatus_Success, esr.Status)
   428  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   429  			BlockHeight: bc.TipHeight() + 1,
   430  		})
   431  		cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0)
   432  		require.NoError(err)
   433  		require.Equal(5, len(cands))
   434  		t.Run("endorsement is withdrawing, candidate can also be chosen as delegate", func(t *testing.T) {
   435  			ctx, err = bc.Context(ctx)
   436  			require.NoError(err)
   437  			ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   438  				BlockHeight: bc.TipHeight(),
   439  			})
   440  			ctx = protocol.WithFeatureCtx(ctx)
   441  			cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0)
   442  			require.NoError(err)
   443  			require.Equal(5, len(cands))
   444  		})
   445  		t.Run("endorsement is expired, candidate can not be chosen as delegate any more", func(t *testing.T) {
   446  			ctx, err = bc.Context(ctx)
   447  			require.NoError(err)
   448  			jumpBlocks(bc, int(cfg.Genesis.EndorsementWithdrawWaitingBlocks), require)
   449  			ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
   450  				BlockHeight: bc.TipHeight(),
   451  			})
   452  			ctx = protocol.WithFeatureCtx(ctx)
   453  			cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0)
   454  			require.NoError(err)
   455  			require.Equal(4, len(cands))
   456  		})
   457  	}
   458  
   459  	cfg := config.Default
   460  	testTriePath, err := testutil.PathOfTempFile("trie")
   461  	require.NoError(err)
   462  	testDBPath, err := testutil.PathOfTempFile("db")
   463  	require.NoError(err)
   464  	testIndexPath, err := testutil.PathOfTempFile("index")
   465  	require.NoError(err)
   466  	testSystemLogPath, err := testutil.PathOfTempFile("systemlog")
   467  	require.NoError(err)
   468  	testSGDIndexPath, err := testutil.PathOfTempFile("sgdindex")
   469  	require.NoError(err)
   470  	defer func() {
   471  		testutil.CleanupPath(testTriePath)
   472  		testutil.CleanupPath(testDBPath)
   473  		testutil.CleanupPath(testIndexPath)
   474  		testutil.CleanupPath(testSystemLogPath)
   475  		testutil.CleanupPath(testSGDIndexPath)
   476  		// clear the gateway
   477  		delete(cfg.Plugins, config.GatewayPlugin)
   478  	}()
   479  
   480  	cfg.ActPool.MinGasPriceStr = "0"
   481  	cfg.Chain.TrieDBPatchFile = ""
   482  	cfg.Chain.TrieDBPath = testTriePath
   483  	cfg.Chain.ChainDBPath = testDBPath
   484  	cfg.Chain.IndexDBPath = testIndexPath
   485  	cfg.Chain.ContractStakingIndexDBPath = testIndexPath
   486  	cfg.Chain.SGDIndexDBPath = testSGDIndexPath
   487  	cfg.System.SystemLogDBPath = testSystemLogPath
   488  	cfg.Consensus.Scheme = config.NOOPScheme
   489  	cfg.Chain.EnableAsyncIndexWrite = false
   490  	cfg.Genesis.BootstrapCandidates = testInitCands
   491  	cfg.Genesis.FbkMigrationBlockHeight = 1
   492  	cfg.Genesis.TsunamiBlockHeight = 0
   493  	cfg.Genesis.EndorsementWithdrawWaitingBlocks = 10
   494  
   495  	t.Run("test native staking", func(t *testing.T) {
   496  		testNativeStaking(cfg, t)
   497  	})
   498  }
   499  
   500  func checkCandidateState(
   501  	sr protocol.StateReader,
   502  	expectedName,
   503  	expectedOwnerAddr string,
   504  	expectedSelfStake,
   505  	expectedVotes *big.Int,
   506  	candidateAddr address.Address,
   507  ) error {
   508  	var cand staking.Candidate
   509  	if _, err := sr.State(&cand, protocol.NamespaceOption(_candidateNameSpace), protocol.KeyOption(candidateAddr.Bytes())); err != nil {
   510  		return err
   511  	}
   512  	if expectedName != cand.Name {
   513  		return errors.New("name does not match")
   514  	}
   515  	if expectedOwnerAddr != cand.Owner.String() {
   516  		return errors.New("Owner address does not match")
   517  	}
   518  	if expectedSelfStake.Cmp(cand.SelfStake) != 0 {
   519  		return errors.New("self stake does not match")
   520  	}
   521  	if expectedVotes.Cmp(cand.Votes) != 0 {
   522  		return errors.New("votes does not match")
   523  	}
   524  	return nil
   525  }
   526  
   527  func checkAccountState(
   528  	cfg config.Config,
   529  	sr protocol.StateReader,
   530  	act *action.SealedEnvelope,
   531  	registrationFee bool,
   532  	expectedBalance *big.Int,
   533  	accountAddr address.Address,
   534  ) error {
   535  	cost, err := act.Cost()
   536  	if err != nil {
   537  		return err
   538  	}
   539  	if registrationFee {
   540  		regFee, ok := new(big.Int).SetString(cfg.Genesis.RegistrationConsts.Fee, 10)
   541  		if !ok {
   542  			return errors.New("failed to set genesis registration fee")
   543  		}
   544  		cost.Add(cost, regFee)
   545  	}
   546  	acct1, err := accountutil.LoadAccount(sr, accountAddr)
   547  	if err != nil {
   548  		return err
   549  	}
   550  	if expectedBalance.Cmp(cost.Add(cost, acct1.Balance)) != 0 {
   551  		return errors.New("balance does not match")
   552  	}
   553  	return nil
   554  }
   555  
   556  func createAndCommitBlock(bc blockchain.Blockchain, ap actpool.ActPool, blkTime time.Time) (*block.Block, error) {
   557  	blk, err := bc.MintNewBlock(blkTime)
   558  	if err != nil {
   559  		return nil, err
   560  	}
   561  	if err := bc.CommitBlock(blk); err != nil {
   562  		return nil, err
   563  	}
   564  	ap.Reset()
   565  	return blk, nil
   566  }