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

     1  // Copyright (c) 2023 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  	"math/big"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	"github.com/stretchr/testify/require"
    16  	"golang.org/x/exp/slices"
    17  
    18  	"github.com/iotexproject/iotex-address/address"
    19  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    20  
    21  	"github.com/iotexproject/iotex-core/action/protocol"
    22  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    23  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    24  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    25  	"github.com/iotexproject/iotex-core/state"
    26  	"github.com/iotexproject/iotex-core/test/identityset"
    27  	"github.com/iotexproject/iotex-core/test/mock/mock_factory"
    28  )
    29  
    30  func TestStakingStateReader(t *testing.T) {
    31  	r := require.New(t)
    32  	contractAddress := "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"
    33  	testCandidates := CandidateList{
    34  		{
    35  			Owner:              identityset.Address(1),
    36  			Operator:           identityset.Address(1),
    37  			Reward:             identityset.Address(1),
    38  			Name:               "cand1",
    39  			Votes:              big.NewInt(10),
    40  			SelfStakeBucketIdx: 0,
    41  			SelfStake:          big.NewInt(10),
    42  		},
    43  		{
    44  			Owner:              identityset.Address(2),
    45  			Operator:           identityset.Address(2),
    46  			Reward:             identityset.Address(2),
    47  			Name:               "cand2",
    48  			Votes:              big.NewInt(0),
    49  			SelfStakeBucketIdx: 0,
    50  			SelfStake:          big.NewInt(0),
    51  		},
    52  	}
    53  	testContractBuckets := []*VoteBucket{
    54  		{
    55  			Index:           1,
    56  			Candidate:       identityset.Address(1),
    57  			Owner:           identityset.Address(1),
    58  			StakedAmount:    big.NewInt(100),
    59  			StakedDuration:  time.Hour * 24,
    60  			CreateTime:      time.Now(),
    61  			StakeStartTime:  time.Now(),
    62  			AutoStake:       true,
    63  			ContractAddress: contractAddress,
    64  		},
    65  		{
    66  			Index:           2,
    67  			Candidate:       identityset.Address(2),
    68  			Owner:           identityset.Address(2),
    69  			StakedAmount:    big.NewInt(100),
    70  			StakedDuration:  time.Hour * 24,
    71  			CreateTime:      time.Now(),
    72  			StakeStartTime:  time.Now(),
    73  			AutoStake:       true,
    74  			ContractAddress: contractAddress,
    75  		},
    76  	}
    77  	testNativeBuckets := []*VoteBucket{
    78  		{
    79  			Index:          1,
    80  			Candidate:      identityset.Address(1),
    81  			Owner:          identityset.Address(1),
    82  			StakedAmount:   big.NewInt(10),
    83  			StakedDuration: time.Hour * 24,
    84  			CreateTime:     time.Now(),
    85  			StakeStartTime: time.Now(),
    86  			AutoStake:      true,
    87  		},
    88  	}
    89  	testNativeTotalAmount := &totalAmount{
    90  		amount: big.NewInt(0),
    91  	}
    92  	for _, b := range testNativeBuckets {
    93  		testNativeTotalAmount.amount.Add(testNativeTotalAmount.amount, b.StakedAmount)
    94  		testNativeTotalAmount.count++
    95  	}
    96  	var err error
    97  	states := make([][]byte, len(testNativeBuckets))
    98  	for i := range states {
    99  		states[i], err = state.Serialize(testNativeBuckets[i])
   100  		r.NoError(err)
   101  	}
   102  	prepare := func(t *testing.T) (*mock_factory.MockFactory, *MockContractStakingIndexer, ReadState, context.Context, *require.Assertions) {
   103  		r := require.New(t)
   104  		ctrl := gomock.NewController(t)
   105  		sf := mock_factory.NewMockFactory(ctrl)
   106  		sf.EXPECT().Height().Return(uint64(1), nil).AnyTimes()
   107  		candCenter, err := NewCandidateCenter(testCandidates)
   108  		r.NoError(err)
   109  		testNativeData := &ViewData{
   110  			candCenter: candCenter,
   111  			bucketPool: &BucketPool{
   112  				total: &totalAmount{
   113  					amount: big.NewInt(100),
   114  					count:  1,
   115  				},
   116  			},
   117  		}
   118  		states := make([][]byte, len(testNativeBuckets))
   119  		for i := range states {
   120  			states[i], err = state.Serialize(testNativeBuckets[i])
   121  			r.NoError(err)
   122  		}
   123  		sf.EXPECT().ReadView(gomock.Any()).Return(testNativeData, nil).Times(1)
   124  
   125  		contractIndexer := NewMockContractStakingIndexer(ctrl)
   126  		contractIndexer.EXPECT().Buckets(gomock.Any()).Return(testContractBuckets, nil).AnyTimes()
   127  
   128  		stakeSR, err := newCompositeStakingStateReader(contractIndexer, nil, sf)
   129  		r.NoError(err)
   130  		r.NotNil(stakeSR)
   131  
   132  		reg := protocol.NewRegistry()
   133  		rolldposProto := rolldpos.NewProtocol(10, 10, 10)
   134  		rolldposProto.Register(reg)
   135  		g := genesis.Default
   136  		g.QuebecBlockHeight = 1
   137  		ctx := genesis.WithGenesisContext(context.Background(), g)
   138  		ctx = protocol.WithRegistry(ctx, reg)
   139  		ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: 1000})
   140  		ctx = protocol.WithFeatureCtx(ctx)
   141  
   142  		return sf, contractIndexer, stakeSR, ctx, r
   143  	}
   144  	addContractVotes := func(expectCand *Candidate) {
   145  		for _, b := range testContractBuckets {
   146  			if b.Candidate.String() == expectCand.Owner.String() {
   147  				expectCand.Votes.Add(expectCand.Votes, b.StakedAmount)
   148  				break
   149  			}
   150  		}
   151  	}
   152  	t.Run("readStateBuckets", func(t *testing.T) {
   153  		sf, _, stakeSR, ctx, r := prepare(t)
   154  		sf.EXPECT().States(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) {
   155  			iter := state.NewIterator(states)
   156  			return uint64(1), iter, nil
   157  		}).Times(1)
   158  		sf.EXPECT().State(gomock.Any(), gomock.Any()).Return(uint64(0), state.ErrStateNotExist).Times(1)
   159  
   160  		req := &iotexapi.ReadStakingDataRequest_VoteBuckets{
   161  			Pagination: &iotexapi.PaginationParam{
   162  				Offset: 0,
   163  				Limit:  100,
   164  			},
   165  		}
   166  		buckets, height, err := stakeSR.readStateBuckets(ctx, req)
   167  		r.NoError(err)
   168  		r.EqualValues(1, height)
   169  		r.Len(buckets.Buckets, len(testNativeBuckets)+len(testContractBuckets))
   170  		for i := range testNativeBuckets {
   171  			iotexBucket, err := testNativeBuckets[i].toIoTeXTypes()
   172  			r.NoError(err)
   173  			r.Equal(iotexBucket, buckets.Buckets[i])
   174  		}
   175  		for i := range testContractBuckets {
   176  			iotexBucket, err := testContractBuckets[i].toIoTeXTypes()
   177  			r.NoError(err)
   178  			r.Equal(iotexBucket, buckets.Buckets[i+len(testNativeBuckets)])
   179  		}
   180  	})
   181  	t.Run("readStateBucketsWithEndorsement", func(t *testing.T) {
   182  		sf, _, stakeSR, ctx, r := prepare(t)
   183  		sf.EXPECT().States(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) {
   184  			iter := state.NewIterator(states)
   185  			return uint64(1), iter, nil
   186  		}).Times(1)
   187  		sf.EXPECT().State(gomock.AssignableToTypeOf(&Endorsement{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   188  			arg0R := arg0.(*Endorsement)
   189  			*arg0R = Endorsement{ExpireHeight: 100}
   190  			return uint64(1), nil
   191  		}).AnyTimes()
   192  
   193  		req := &iotexapi.ReadStakingDataRequest_VoteBuckets{
   194  			Pagination: &iotexapi.PaginationParam{
   195  				Offset: 0,
   196  				Limit:  100,
   197  			},
   198  		}
   199  		buckets, height, err := stakeSR.readStateBuckets(ctx, req)
   200  		r.NoError(err)
   201  		r.EqualValues(1, height)
   202  		r.Len(buckets.Buckets, len(testNativeBuckets)+len(testContractBuckets))
   203  		iotexBuckets, err := toIoTeXTypesVoteBucketList(sf, testNativeBuckets)
   204  		r.NoError(err)
   205  		for i := range testNativeBuckets {
   206  			r.Equal(iotexBuckets.Buckets[i], buckets.Buckets[i])
   207  		}
   208  		iotexBuckets, err = toIoTeXTypesVoteBucketList(sf, testContractBuckets)
   209  		r.NoError(err)
   210  		for i := range testContractBuckets {
   211  			r.Equal(iotexBuckets.Buckets[i], buckets.Buckets[i+len(testNativeBuckets)])
   212  		}
   213  	})
   214  
   215  	t.Run("readStateBucketsByVoter", func(t *testing.T) {
   216  		sf, _, stakeSR, ctx, r := prepare(t)
   217  		sf.EXPECT().State(gomock.AssignableToTypeOf(&BucketIndices{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   218  			arg0R := arg0.(*BucketIndices)
   219  			*arg0R = []uint64{0}
   220  			return uint64(1), nil
   221  		}).Times(1)
   222  		sf.EXPECT().State(gomock.AssignableToTypeOf(&VoteBucket{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   223  			arg0R := arg0.(*VoteBucket)
   224  			cfg := &protocol.StateConfig{}
   225  			if err := arg1[1](cfg); err != nil {
   226  				return uint64(0), err
   227  			}
   228  			idx := byteutil.BytesToUint64BigEndian(cfg.Key[1:])
   229  			*arg0R = *testNativeBuckets[idx]
   230  			return uint64(1), nil
   231  		}).Times(1)
   232  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalBucketCount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   233  			arg0R := arg0.(*totalBucketCount)
   234  			*arg0R = totalBucketCount{count: 1}
   235  			return uint64(1), nil
   236  		}).Times(1)
   237  		sf.EXPECT().State(gomock.AssignableToTypeOf(&Endorsement{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   238  			return uint64(0), state.ErrStateNotExist
   239  		}).Times(1)
   240  
   241  		req := &iotexapi.ReadStakingDataRequest_VoteBucketsByVoter{
   242  			Pagination: &iotexapi.PaginationParam{
   243  				Offset: 0,
   244  				Limit:  100,
   245  			},
   246  			VoterAddress: identityset.Address(1).String(),
   247  		}
   248  		buckets, height, err := stakeSR.readStateBucketsByVoter(ctx, req)
   249  		r.NoError(err)
   250  		r.EqualValues(1, height)
   251  		r.Len(buckets.Buckets, 2)
   252  		iotexBucket, err := testNativeBuckets[0].toIoTeXTypes()
   253  		r.NoError(err)
   254  		r.Equal(iotexBucket, buckets.Buckets[0])
   255  		iotexBucket, err = testContractBuckets[0].toIoTeXTypes()
   256  		r.NoError(err)
   257  		r.Equal(iotexBucket, buckets.Buckets[1])
   258  	})
   259  	t.Run("readStateBucketsByCandidate", func(t *testing.T) {
   260  		sf, contractIndexer, stakeSR, ctx, r := prepare(t)
   261  		sf.EXPECT().State(gomock.AssignableToTypeOf(&VoteBucket{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   262  			arg0R := arg0.(*VoteBucket)
   263  			cfg := &protocol.StateConfig{}
   264  			if err := arg1[1](cfg); err != nil {
   265  				return uint64(0), err
   266  			}
   267  			idx := byteutil.BytesToUint64BigEndian(cfg.Key[1:])
   268  			*arg0R = *testNativeBuckets[idx]
   269  			return uint64(1), nil
   270  		}).Times(1)
   271  		sf.EXPECT().State(gomock.AssignableToTypeOf(&BucketIndices{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   272  			arg0R := arg0.(*BucketIndices)
   273  			*arg0R = []uint64{0}
   274  			return uint64(1), nil
   275  		}).Times(1)
   276  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalBucketCount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   277  			arg0R := arg0.(*totalBucketCount)
   278  			*arg0R = totalBucketCount{count: 1}
   279  			return uint64(1), nil
   280  		}).Times(1)
   281  		sf.EXPECT().State(gomock.AssignableToTypeOf(&Endorsement{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   282  			return uint64(0), state.ErrStateNotExist
   283  		}).Times(1)
   284  		contractIndexer.EXPECT().BucketsByCandidate(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 address.Address, arg1 uint64) ([]*VoteBucket, error) {
   285  			buckets := []*VoteBucket{}
   286  			for i := range testContractBuckets {
   287  				if testContractBuckets[i].Candidate.String() == arg0.String() {
   288  					buckets = append(buckets, testContractBuckets[i])
   289  				}
   290  			}
   291  			return buckets, nil
   292  		}).Times(1)
   293  		req := &iotexapi.ReadStakingDataRequest_VoteBucketsByCandidate{
   294  			Pagination: &iotexapi.PaginationParam{
   295  				Offset: 0,
   296  				Limit:  100,
   297  			},
   298  			CandName: "cand1",
   299  		}
   300  		buckets, height, err := stakeSR.readStateBucketsByCandidate(ctx, req)
   301  		r.NoError(err)
   302  		r.EqualValues(1, height)
   303  		r.Len(buckets.Buckets, 2)
   304  		iotexBucket, err := testNativeBuckets[0].toIoTeXTypes()
   305  		r.NoError(err)
   306  		r.Equal(iotexBucket, buckets.Buckets[0])
   307  		iotexBucket, err = testContractBuckets[0].toIoTeXTypes()
   308  		r.NoError(err)
   309  		r.Equal(iotexBucket, buckets.Buckets[1])
   310  	})
   311  	t.Run("readStateBucketByIndices", func(t *testing.T) {
   312  		sf, contractIndexer, stakeSR, ctx, r := prepare(t)
   313  		sf.EXPECT().State(gomock.AssignableToTypeOf(&VoteBucket{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   314  			arg0R := arg0.(*VoteBucket)
   315  			cfg := &protocol.StateConfig{}
   316  			if err := arg1[1](cfg); err != nil {
   317  				return uint64(0), err
   318  			}
   319  			idx := byteutil.BytesToUint64BigEndian(cfg.Key[1:])
   320  			*arg0R = *testNativeBuckets[idx]
   321  			return uint64(1), nil
   322  		}).Times(1)
   323  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalBucketCount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   324  			arg0R := arg0.(*totalBucketCount)
   325  			*arg0R = totalBucketCount{count: 1}
   326  			return uint64(1), nil
   327  		}).Times(1)
   328  		sf.EXPECT().State(gomock.AssignableToTypeOf(&Endorsement{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   329  			return uint64(0), state.ErrStateNotExist
   330  		}).Times(1)
   331  		contractIndexer.EXPECT().BucketsByIndices(gomock.Any(), gomock.Any()).DoAndReturn(func(arg0 []uint64, arg1 uint64) ([]*VoteBucket, error) {
   332  			buckets := []*VoteBucket{}
   333  			for i := range arg0 {
   334  				buckets = append(buckets, testContractBuckets[arg0[i]])
   335  			}
   336  			return buckets, nil
   337  		}).Times(1)
   338  		req := &iotexapi.ReadStakingDataRequest_VoteBucketsByIndexes{
   339  			Index: []uint64{0},
   340  		}
   341  		buckets, height, err := stakeSR.readStateBucketByIndices(ctx, req)
   342  		r.NoError(err)
   343  		r.EqualValues(1, height)
   344  		r.Len(buckets.Buckets, 2)
   345  		iotexBucket, err := testNativeBuckets[0].toIoTeXTypes()
   346  		r.NoError(err)
   347  		r.Equal(iotexBucket, buckets.Buckets[0])
   348  		iotexBucket, err = testContractBuckets[0].toIoTeXTypes()
   349  		r.NoError(err)
   350  		r.Equal(iotexBucket, buckets.Buckets[1])
   351  	})
   352  	t.Run("readStateBucketCount", func(t *testing.T) {
   353  		sf, contractIndexer, stakeSR, ctx, r := prepare(t)
   354  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalAmount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   355  			arg0R := arg0.(*totalAmount)
   356  			*arg0R = *testNativeTotalAmount
   357  			return uint64(1), nil
   358  		}).Times(1)
   359  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalBucketCount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   360  			arg0R := arg0.(*totalBucketCount)
   361  			*arg0R = totalBucketCount{count: 1}
   362  			return uint64(1), nil
   363  		}).Times(1)
   364  		contractIndexer.EXPECT().TotalBucketCount(gomock.Any()).Return(uint64(len(testContractBuckets)), nil).Times(1)
   365  		cfg := genesis.Default
   366  		cfg.GreenlandBlockHeight = 0
   367  		ctx = genesis.WithGenesisContext(ctx, cfg)
   368  		ctx = protocol.WithFeatureWithHeightCtx(ctx)
   369  		req := &iotexapi.ReadStakingDataRequest_BucketsCount{}
   370  		bucketCount, height, err := stakeSR.readStateBucketCount(ctx, req)
   371  		r.NoError(err)
   372  		r.EqualValues(1, height)
   373  		r.EqualValues(3, bucketCount.Total)
   374  		r.EqualValues(3, bucketCount.Active)
   375  	})
   376  	t.Run("readStateCandidates", func(t *testing.T) {
   377  		_, contractIndexer, stakeSR, ctx, r := prepare(t)
   378  		contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
   379  			for _, b := range testContractBuckets {
   380  				if b.Owner.String() == ownerAddr.String() {
   381  					return b.StakedAmount, nil
   382  				}
   383  			}
   384  			return big.NewInt(0), nil
   385  		}).MinTimes(1)
   386  		req := &iotexapi.ReadStakingDataRequest_Candidates{
   387  			Pagination: &iotexapi.PaginationParam{
   388  				Offset: 0,
   389  				Limit:  100,
   390  			},
   391  		}
   392  		candidates, height, err := stakeSR.readStateCandidates(ctx, req)
   393  		r.NoError(err)
   394  		r.EqualValues(1, height)
   395  		r.Len(candidates.Candidates, 2)
   396  		for i := range candidates.Candidates {
   397  			idx := slices.IndexFunc(testCandidates, func(c *Candidate) bool {
   398  				return c.Owner.String() == candidates.Candidates[i].OwnerAddress
   399  			})
   400  			r.True(idx >= 0)
   401  			expectCand := *testCandidates[idx]
   402  			addContractVotes(&expectCand)
   403  			r.EqualValues(expectCand.toIoTeXTypes(), candidates.Candidates[i])
   404  		}
   405  	})
   406  	t.Run("readStateCandidateByName", func(t *testing.T) {
   407  		_, contractIndexer, stakeSR, ctx, r := prepare(t)
   408  		contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
   409  			for _, b := range testContractBuckets {
   410  				if b.Owner.String() == ownerAddr.String() {
   411  					return b.StakedAmount, nil
   412  				}
   413  			}
   414  			return big.NewInt(0), nil
   415  		}).MinTimes(1)
   416  		req := &iotexapi.ReadStakingDataRequest_CandidateByName{
   417  			CandName: "cand1",
   418  		}
   419  		candidate, _, err := stakeSR.readStateCandidateByName(ctx, req)
   420  		r.NoError(err)
   421  		idx := slices.IndexFunc(testCandidates, func(c *Candidate) bool {
   422  			return c.Owner.String() == candidate.OwnerAddress
   423  		})
   424  		r.True(idx >= 0)
   425  		expectCand := *testCandidates[idx]
   426  		addContractVotes(&expectCand)
   427  		r.EqualValues(expectCand.toIoTeXTypes(), candidate)
   428  	})
   429  	t.Run("readStateCandidateByAddress", func(t *testing.T) {
   430  		_, contractIndexer, stakeSR, ctx, r := prepare(t)
   431  		contractIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, ownerAddr address.Address, height uint64) (*big.Int, error) {
   432  			for _, b := range testContractBuckets {
   433  				if b.Owner.String() == ownerAddr.String() {
   434  					return b.StakedAmount, nil
   435  				}
   436  			}
   437  			return big.NewInt(0), nil
   438  		}).MinTimes(1)
   439  		req := &iotexapi.ReadStakingDataRequest_CandidateByAddress{
   440  			OwnerAddr: identityset.Address(1).String(),
   441  		}
   442  		candidate, _, err := stakeSR.readStateCandidateByAddress(ctx, req)
   443  		r.NoError(err)
   444  		idx := slices.IndexFunc(testCandidates, func(c *Candidate) bool {
   445  			return c.Owner.String() == candidate.OwnerAddress
   446  		})
   447  		r.True(idx >= 0)
   448  		expectCand := *testCandidates[idx]
   449  		addContractVotes(&expectCand)
   450  		r.EqualValues(expectCand.toIoTeXTypes(), candidate)
   451  	})
   452  	t.Run("readStateTotalStakingAmount", func(t *testing.T) {
   453  		sf, _, stakeSR, ctx, r := prepare(t)
   454  		sf.EXPECT().State(gomock.AssignableToTypeOf(&totalAmount{}), gomock.Any()).DoAndReturn(func(arg0 any, arg1 ...protocol.StateOption) (uint64, error) {
   455  			arg0R := arg0.(*totalAmount)
   456  			*arg0R = *testNativeTotalAmount
   457  			return uint64(1), nil
   458  		}).Times(1)
   459  		cfg := genesis.Default
   460  		cfg.GreenlandBlockHeight = 0
   461  		ctx = genesis.WithGenesisContext(ctx, cfg)
   462  		ctx = protocol.WithFeatureWithHeightCtx(ctx)
   463  		req := &iotexapi.ReadStakingDataRequest_TotalStakingAmount{}
   464  		total, height, err := stakeSR.readStateTotalStakingAmount(ctx, req)
   465  		r.NoError(err)
   466  		r.EqualValues(1, height)
   467  		r.EqualValues("210", total.Balance)
   468  	})
   469  }