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

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package rewarding
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  	"testing"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/iotexproject/go-pkgs/hash"
    18  	"github.com/iotexproject/iotex-address/address"
    19  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    20  
    21  	"github.com/iotexproject/iotex-core/action"
    22  	"github.com/iotexproject/iotex-core/action/protocol"
    23  	"github.com/iotexproject/iotex-core/action/protocol/account"
    24  	"github.com/iotexproject/iotex-core/action/protocol/poll"
    25  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    26  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    27  	"github.com/iotexproject/iotex-core/db/batch"
    28  	"github.com/iotexproject/iotex-core/pkg/unit"
    29  	"github.com/iotexproject/iotex-core/state"
    30  	"github.com/iotexproject/iotex-core/test/identityset"
    31  	"github.com/iotexproject/iotex-core/test/mock/mock_chainmanager"
    32  	"github.com/iotexproject/iotex-core/test/mock/mock_poll"
    33  	"github.com/iotexproject/iotex-core/testutil/testdb"
    34  )
    35  
    36  func TestValidateExtension(t *testing.T) {
    37  	r := require.New(t)
    38  
    39  	g := genesis.Default.Rewarding
    40  	r.NoError(validateFoundationBonusExtension(g))
    41  
    42  	last := g.FoundationBonusP2StartEpoch
    43  	g.FoundationBonusP2StartEpoch = g.FoundationBonusLastEpoch - 1
    44  	r.Equal(errInvalidEpoch, validateFoundationBonusExtension(g))
    45  	g.FoundationBonusP2StartEpoch = last
    46  
    47  	last = g.FoundationBonusP2EndEpoch
    48  	g.FoundationBonusP2EndEpoch = g.FoundationBonusP2StartEpoch - 1
    49  	r.Equal(errInvalidEpoch, validateFoundationBonusExtension(g))
    50  	g.FoundationBonusP2EndEpoch = last
    51  }
    52  
    53  func testProtocol(t *testing.T, test func(*testing.T, context.Context, protocol.StateManager, *Protocol), withExempt bool) {
    54  	ctrl := gomock.NewController(t)
    55  
    56  	registry := protocol.NewRegistry()
    57  	sm := testdb.NewMockStateManager(ctrl)
    58  
    59  	g := genesis.Default
    60  	// Create a test account with 1000 token
    61  	g.InitBalanceMap[identityset.Address(28).String()] = "1000"
    62  	g.Rewarding.InitBalanceStr = "0"
    63  	g.Rewarding.ExemptAddrStrsFromEpochReward = []string{}
    64  	g.Rewarding.BlockRewardStr = "10"
    65  	g.Rewarding.EpochRewardStr = "100"
    66  	g.Rewarding.NumDelegatesForEpochReward = 4
    67  	g.Rewarding.FoundationBonusStr = "5"
    68  	g.Rewarding.NumDelegatesForFoundationBonus = 5
    69  	g.Rewarding.FoundationBonusLastEpoch = 365
    70  	g.Rewarding.ProductivityThreshold = 50
    71  	// Initialize the protocol
    72  	if withExempt {
    73  		g.Rewarding.ExemptAddrStrsFromEpochReward = []string{
    74  			identityset.Address(31).String(),
    75  		}
    76  		g.Rewarding.NumDelegatesForEpochReward = 10
    77  	}
    78  	rp := rolldpos.NewProtocol(
    79  		g.NumCandidateDelegates,
    80  		g.NumDelegates,
    81  		g.NumSubEpochs,
    82  		rolldpos.EnableDardanellesSubEpoch(g.DardanellesBlockHeight, g.DardanellesNumSubEpochs),
    83  	)
    84  	p := NewProtocol(g.Rewarding)
    85  	candidates := []*state.Candidate{
    86  		{
    87  			Address:       identityset.Address(27).String(),
    88  			Votes:         unit.ConvertIotxToRau(4000000),
    89  			RewardAddress: identityset.Address(0).String(),
    90  		},
    91  		{
    92  			Address:       identityset.Address(28).String(),
    93  			Votes:         unit.ConvertIotxToRau(3000000),
    94  			RewardAddress: identityset.Address(28).String(),
    95  		},
    96  		{
    97  			Address:       identityset.Address(29).String(),
    98  			Votes:         unit.ConvertIotxToRau(2000000),
    99  			RewardAddress: identityset.Address(29).String(),
   100  		},
   101  		{
   102  			Address:       identityset.Address(30).String(),
   103  			Votes:         unit.ConvertIotxToRau(1000000),
   104  			RewardAddress: identityset.Address(30).String(),
   105  		},
   106  		{
   107  			Address:       identityset.Address(31).String(),
   108  			Votes:         unit.ConvertIotxToRau(500000),
   109  			RewardAddress: identityset.Address(31).String(),
   110  		},
   111  		{
   112  			Address:       identityset.Address(32).String(),
   113  			Votes:         unit.ConvertIotxToRau(500000),
   114  			RewardAddress: identityset.Address(32).String(),
   115  		},
   116  	}
   117  	abps := []*state.Candidate{
   118  		{
   119  			Address:       identityset.Address(27).String(),
   120  			Votes:         unit.ConvertIotxToRau(4000000),
   121  			RewardAddress: identityset.Address(0).String(),
   122  		},
   123  		{
   124  			Address:       identityset.Address(28).String(),
   125  			Votes:         unit.ConvertIotxToRau(3000000),
   126  			RewardAddress: identityset.Address(28).String(),
   127  		},
   128  		{
   129  			Address:       identityset.Address(29).String(),
   130  			Votes:         unit.ConvertIotxToRau(2000000),
   131  			RewardAddress: identityset.Address(29).String(),
   132  		},
   133  		{
   134  			Address:       identityset.Address(30).String(),
   135  			Votes:         unit.ConvertIotxToRau(1000000),
   136  			RewardAddress: identityset.Address(30).String(),
   137  		},
   138  		{
   139  			Address:       identityset.Address(31).String(),
   140  			Votes:         unit.ConvertIotxToRau(500000),
   141  			RewardAddress: identityset.Address(31).String(),
   142  		},
   143  	}
   144  	pp := mock_poll.NewMockProtocol(ctrl)
   145  	pp.EXPECT().Candidates(gomock.Any(), gomock.Any()).Return(candidates, nil).AnyTimes()
   146  	pp.EXPECT().Delegates(gomock.Any(), gomock.Any()).Return(abps, nil).AnyTimes()
   147  	pp.EXPECT().Register(gomock.Any()).DoAndReturn(func(reg *protocol.Registry) error {
   148  		return reg.Register("poll", pp)
   149  	}).AnyTimes()
   150  	pp.EXPECT().CalculateUnproductiveDelegates(gomock.Any(), gomock.Any()).Return(
   151  		[]string{
   152  			identityset.Address(29).String(),
   153  			identityset.Address(31).String(),
   154  		}, nil,
   155  	).AnyTimes()
   156  	require.NoError(t, rp.Register(registry))
   157  	require.NoError(t, pp.Register(registry))
   158  	require.NoError(t, p.Register(registry))
   159  
   160  	ctx := protocol.WithBlockCtx(
   161  		context.Background(),
   162  		protocol.BlockCtx{
   163  			BlockHeight: 0,
   164  		},
   165  	)
   166  	ctx = genesis.WithGenesisContext(ctx, g)
   167  	ctx = protocol.WithFeatureCtx(ctx)
   168  	ap := account.NewProtocol(DepositGas)
   169  	require.NoError(t, ap.Register(registry))
   170  	require.NoError(t, ap.CreateGenesisStates(ctx, sm))
   171  	require.NoError(t, p.CreateGenesisStates(ctx, sm))
   172  
   173  	ctx = protocol.WithBlockCtx(
   174  		ctx, protocol.BlockCtx{
   175  			Producer:    identityset.Address(27),
   176  			BlockHeight: genesis.Default.NumDelegates * genesis.Default.NumSubEpochs,
   177  		},
   178  	)
   179  	ctx = protocol.WithActionCtx(
   180  		ctx, protocol.ActionCtx{
   181  			Caller: identityset.Address(28),
   182  		},
   183  	)
   184  	ctx = protocol.WithBlockchainCtx(
   185  		protocol.WithRegistry(ctx, registry), protocol.BlockchainCtx{
   186  			Tip: protocol.TipInfo{
   187  				Height: 20,
   188  			},
   189  		},
   190  	)
   191  	blockReward, err := p.BlockReward(ctx, sm)
   192  	require.NoError(t, err)
   193  	assert.Equal(t, big.NewInt(10), blockReward)
   194  	epochReward, err := p.EpochReward(ctx, sm)
   195  	require.NoError(t, err)
   196  	assert.Equal(t, big.NewInt(100), epochReward)
   197  	fb, err := p.FoundationBonus(ctx, sm)
   198  	require.NoError(t, err)
   199  	assert.Equal(t, big.NewInt(5), fb)
   200  	ndffb, err := p.NumDelegatesForFoundationBonus(ctx, sm)
   201  	require.NoError(t, err)
   202  	assert.Equal(t, uint64(5), ndffb)
   203  	fble, err := p.FoundationBonusLastEpoch(ctx, sm)
   204  	require.NoError(t, err)
   205  	assert.Equal(t, uint64(365), fble)
   206  	pt, err := p.ProductivityThreshold(ctx, sm)
   207  	require.NoError(t, err)
   208  	assert.Equal(t, uint64(50), pt)
   209  
   210  	totalBalance, _, err := p.TotalBalance(ctx, sm)
   211  	require.NoError(t, err)
   212  	assert.Equal(t, big.NewInt(0), totalBalance)
   213  	availableBalance, _, err := p.AvailableBalance(ctx, sm)
   214  	require.NoError(t, err)
   215  	assert.Equal(t, big.NewInt(0), availableBalance)
   216  
   217  	test(t, ctx, sm, p)
   218  }
   219  
   220  func TestProtocol_Validate(t *testing.T) {
   221  	g := genesis.Default
   222  	g.NewfoundlandBlockHeight = 0
   223  	p := NewProtocol(g.Rewarding)
   224  	act := createGrantRewardAction(0, uint64(0)).Action()
   225  	ctx := protocol.WithBlockCtx(
   226  		context.Background(),
   227  		protocol.BlockCtx{
   228  			Producer:    identityset.Address(0),
   229  			BlockHeight: genesis.Default.NumDelegates * genesis.Default.NumSubEpochs,
   230  		},
   231  	)
   232  	ctx = genesis.WithGenesisContext(
   233  		ctx,
   234  		genesis.Genesis{},
   235  	)
   236  	ctx = protocol.WithActionCtx(
   237  		protocol.WithFeatureCtx(ctx),
   238  		protocol.ActionCtx{
   239  			Caller:   identityset.Address(0),
   240  			GasPrice: big.NewInt(0),
   241  		},
   242  	)
   243  	require.NoError(t, p.Validate(ctx, act, nil))
   244  	ctx = protocol.WithActionCtx(
   245  		ctx,
   246  		protocol.ActionCtx{
   247  			Caller:   identityset.Address(1),
   248  			GasPrice: big.NewInt(0),
   249  		},
   250  	)
   251  	require.Error(t, p.Validate(ctx, act, nil))
   252  	ctx = protocol.WithActionCtx(
   253  		ctx,
   254  		protocol.ActionCtx{
   255  			Caller:   identityset.Address(0),
   256  			GasPrice: big.NewInt(1),
   257  		},
   258  	)
   259  	require.Error(t, p.Validate(ctx, act, nil))
   260  	require.Error(t, p.Validate(ctx, act, nil))
   261  	ctx = protocol.WithActionCtx(
   262  		ctx,
   263  		protocol.ActionCtx{
   264  			Caller:       identityset.Address(0),
   265  			GasPrice:     big.NewInt(0),
   266  			IntrinsicGas: 1,
   267  		},
   268  	)
   269  	require.Error(t, p.Validate(ctx, act, nil))
   270  }
   271  
   272  func TestProtocol_Handle(t *testing.T) {
   273  	ctrl := gomock.NewController(t)
   274  
   275  	g := genesis.Default
   276  	registry := protocol.NewRegistry()
   277  	sm := mock_chainmanager.NewMockStateManager(ctrl)
   278  	cb := batch.NewCachedBatch()
   279  	sm.EXPECT().State(gomock.Any(), gomock.Any()).DoAndReturn(
   280  		func(account interface{}, opts ...protocol.StateOption) (uint64, error) {
   281  			cfg, err := protocol.CreateStateConfig(opts...)
   282  			if err != nil {
   283  				return 0, err
   284  			}
   285  			val, err := cb.Get("state", cfg.Key)
   286  			if err != nil {
   287  				return 0, state.ErrStateNotExist
   288  			}
   289  			return 0, state.Deserialize(account, val)
   290  		}).AnyTimes()
   291  	sm.EXPECT().PutState(gomock.Any(), gomock.Any()).DoAndReturn(
   292  		func(account interface{}, opts ...protocol.StateOption) (uint64, error) {
   293  			cfg, err := protocol.CreateStateConfig(opts...)
   294  			if err != nil {
   295  				return 0, err
   296  			}
   297  			ss, err := state.Serialize(account)
   298  			if err != nil {
   299  				return 0, err
   300  			}
   301  			cb.Put("state", cfg.Key, ss, "failed to put state")
   302  			return 0, nil
   303  		}).AnyTimes()
   304  	sm.EXPECT().Snapshot().Return(1).AnyTimes()
   305  	sm.EXPECT().Revert(gomock.Any()).Return(nil).AnyTimes()
   306  
   307  	g.Rewarding.InitBalanceStr = "1000000"
   308  	g.Rewarding.BlockRewardStr = "10"
   309  	g.Rewarding.EpochRewardStr = "100"
   310  	g.Rewarding.NumDelegatesForEpochReward = 10
   311  	g.Rewarding.ExemptAddrStrsFromEpochReward = []string{}
   312  	g.Rewarding.FoundationBonusStr = "5"
   313  	g.Rewarding.NumDelegatesForFoundationBonus = 5
   314  	g.Rewarding.FoundationBonusLastEpoch = 0
   315  	g.Rewarding.ProductivityThreshold = 50
   316  	// Create a test account with 1000000 token
   317  	g.InitBalanceMap[identityset.Address(0).String()] = "1000000"
   318  	g.NumSubEpochs = 15
   319  	rp := rolldpos.NewProtocol(
   320  		g.NumCandidateDelegates,
   321  		g.NumDelegates,
   322  		g.NumSubEpochs,
   323  		rolldpos.EnableDardanellesSubEpoch(g.DardanellesBlockHeight, g.DardanellesNumSubEpochs),
   324  	)
   325  	require.Equal(t, g.FairbankBlockHeight, rp.GetEpochHeight(g.FoundationBonusP2StartEpoch))
   326  	require.Equal(t, g.FoundationBonusP2StartEpoch, rp.GetEpochNum(g.FairbankBlockHeight))
   327  	require.Equal(t, g.FoundationBonusP2EndEpoch, g.FoundationBonusP2StartEpoch+24*365)
   328  	require.NoError(t, rp.Register(registry))
   329  	pp := poll.NewLifeLongDelegatesProtocol(g.Delegates)
   330  	require.NoError(t, pp.Register(registry))
   331  	p := NewProtocol(g.Rewarding)
   332  	require.NoError(t, p.Register(registry))
   333  	// Test for ForceRegister
   334  	require.NoError(t, p.ForceRegister(registry))
   335  
   336  	// address package also defined protocol address, make sure they match
   337  	require.Equal(t, p.addr.Bytes(), address.RewardingProtocolAddrHash[:])
   338  	rwdAddr, err := address.FromString(address.RewardingProtocol)
   339  	require.NoError(t, err)
   340  	require.Equal(t, p.addr.Bytes(), rwdAddr.Bytes())
   341  
   342  	ctx := protocol.WithBlockCtx(
   343  		context.Background(),
   344  		protocol.BlockCtx{
   345  			BlockHeight: 0,
   346  		},
   347  	)
   348  
   349  	ctx = genesis.WithGenesisContext(protocol.WithRegistry(ctx, registry), g)
   350  	ctx = protocol.WithFeatureCtx(ctx)
   351  	ap := account.NewProtocol(DepositGas)
   352  	require.NoError(t, ap.Register(registry))
   353  	require.NoError(t, ap.CreateGenesisStates(ctx, sm))
   354  	require.NoError(t, p.CreateGenesisStates(ctx, sm))
   355  
   356  	ctx = protocol.WithBlockCtx(
   357  		ctx,
   358  		protocol.BlockCtx{
   359  			Producer:    identityset.Address(0),
   360  			BlockHeight: genesis.Default.NumDelegates * genesis.Default.NumSubEpochs,
   361  		},
   362  	)
   363  	ctx = protocol.WithActionCtx(
   364  		ctx,
   365  		protocol.ActionCtx{
   366  			Caller:   identityset.Address(0),
   367  			GasPrice: big.NewInt(0),
   368  			Nonce:    1,
   369  		},
   370  	)
   371  
   372  	// Deposit
   373  	db := action.DepositToRewardingFundBuilder{}
   374  	deposit := db.SetAmount(big.NewInt(1000000)).Build()
   375  	eb1 := action.EnvelopeBuilder{}
   376  	e1 := eb1.SetNonce(1).
   377  		SetGasPrice(big.NewInt(0)).
   378  		SetGasLimit(deposit.GasLimit()).
   379  		SetAction(&deposit).
   380  		Build()
   381  	se1, err := action.Sign(e1, identityset.PrivateKey(0))
   382  	require.NoError(t, err)
   383  
   384  	_, err = p.Handle(ctx, se1.Action(), sm)
   385  	require.NoError(t, err)
   386  	balance, _, err := p.TotalBalance(ctx, sm)
   387  	require.NoError(t, err)
   388  	assert.Equal(t, big.NewInt(2000000), balance)
   389  
   390  	// Grant
   391  	// Test for createGrantRewardAction
   392  	e2 := createGrantRewardAction(0, uint64(0))
   393  	se2, err := action.Sign(e2, identityset.PrivateKey(0))
   394  	require.NoError(t, err)
   395  	ctx = protocol.WithActionCtx(
   396  		ctx,
   397  		protocol.ActionCtx{
   398  			Caller:   identityset.Address(0),
   399  			GasPrice: big.NewInt(0),
   400  			Nonce:    0,
   401  		},
   402  	)
   403  	receipt, err := p.Handle(ctx, se2.Action(), sm)
   404  	require.NoError(t, err)
   405  	assert.Equal(t, uint64(iotextypes.ReceiptStatus_Success), receipt.Status)
   406  	assert.Equal(t, 1, len(receipt.Logs()))
   407  	ctx = protocol.WithActionCtx(
   408  		ctx,
   409  		protocol.ActionCtx{
   410  			Caller:   identityset.Address(0),
   411  			GasPrice: big.NewInt(0),
   412  			Nonce:    0,
   413  		},
   414  	)
   415  	// Grant the block reward again should fail
   416  	receipt, err = p.Handle(ctx, se2.Action(), sm)
   417  	require.NoError(t, err)
   418  	assert.Equal(t, uint64(iotextypes.ReceiptStatus_Failure), receipt.Status)
   419  
   420  	// Claim
   421  	claimBuilder := action.ClaimFromRewardingFundBuilder{}
   422  	claim := claimBuilder.SetAmount(big.NewInt(1000000)).Build()
   423  	eb3 := action.EnvelopeBuilder{}
   424  	e3 := eb3.SetNonce(4).
   425  		SetGasPrice(big.NewInt(0)).
   426  		SetGasLimit(claim.GasLimit()).
   427  		SetAction(&claim).
   428  		Build()
   429  	se3, err := action.Sign(e3, identityset.PrivateKey(0))
   430  	require.NoError(t, err)
   431  	ctx = protocol.WithActionCtx(
   432  		ctx,
   433  		protocol.ActionCtx{
   434  			Caller:   identityset.Address(0),
   435  			GasPrice: big.NewInt(0),
   436  			Nonce:    2,
   437  		},
   438  	)
   439  	_, err = p.Handle(ctx, se3.Action(), sm)
   440  	require.NoError(t, err)
   441  	balance, _, err = p.TotalBalance(ctx, sm)
   442  	require.NoError(t, err)
   443  	assert.Equal(t, big.NewInt(1000000), balance)
   444  
   445  	// Test CreatePreStates
   446  	ctx = protocol.WithBlockCtx(
   447  		ctx,
   448  		protocol.BlockCtx{
   449  			BlockHeight: 1816201,
   450  		},
   451  	)
   452  	require.NoError(t, p.CreatePreStates(ctx, sm))
   453  	blockReward, err := p.BlockReward(ctx, sm)
   454  	require.NoError(t, err)
   455  	assert.Equal(t, big.NewInt(8000000000000000000), blockReward)
   456  
   457  	ctx = protocol.WithBlockCtx(
   458  		ctx,
   459  		protocol.BlockCtx{
   460  			BlockHeight: 864001,
   461  		},
   462  	)
   463  	require.NoError(t, p.CreatePreStates(ctx, sm))
   464  	BlockReward, err := p.BlockReward(ctx, sm)
   465  	require.NoError(t, err)
   466  	assert.Equal(t, big.NewInt(8000000000000000000), BlockReward)
   467  
   468  	// Test for CreatePostSystemActions
   469  	grants, err := p.CreatePostSystemActions(ctx, sm)
   470  	require.NoError(t, err)
   471  	require.NotNil(t, grants)
   472  
   473  	// Test for ReadState
   474  	testMethods := []struct {
   475  		input  string
   476  		expect []byte
   477  	}{
   478  		{
   479  			input:  "AvailableBalance",
   480  			expect: []byte{49, 57, 57, 57, 57, 57, 48},
   481  		},
   482  		{
   483  			input:  "TotalBalance",
   484  			expect: []byte{49, 48, 48, 48, 48, 48, 48},
   485  		},
   486  		{
   487  			input:  "UnclaimedBalance",
   488  			expect: []byte{48},
   489  		},
   490  	}
   491  
   492  	for _, ts := range testMethods {
   493  
   494  		if ts.input == "UnclaimedBalance" {
   495  			UnclaimedBalance, _, err := p.ReadState(ctx, sm, []byte(ts.input), nil)
   496  			require.Nil(t, UnclaimedBalance)
   497  			require.Error(t, err)
   498  
   499  			arg1 := []byte("io1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd39ym7")
   500  			arg2 := []byte("io1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd39ym8")
   501  			UnclaimedBalance, _, err = p.ReadState(ctx, sm, []byte(ts.input), arg1, arg2)
   502  			require.Nil(t, UnclaimedBalance)
   503  			require.Error(t, err)
   504  
   505  			UnclaimedBalance, _, err = p.ReadState(ctx, sm, []byte(ts.input), arg1)
   506  			require.Equal(t, ts.expect, UnclaimedBalance)
   507  			require.NoError(t, err)
   508  			continue
   509  		}
   510  
   511  		output, _, err := p.ReadState(ctx, sm, []byte(ts.input), nil)
   512  		require.NoError(t, err)
   513  		require.Equal(t, ts.expect, output)
   514  	}
   515  
   516  	// Test for deleteState
   517  	sm.EXPECT().DelState(gomock.Any()).DoAndReturn(func(addrHash hash.Hash160) error {
   518  		cb.Delete("state", addrHash[:], "failed to delete state")
   519  		return nil
   520  	}).AnyTimes()
   521  }
   522  
   523  func TestStateCheckLegacy(t *testing.T) {
   524  	require := require.New(t)
   525  
   526  	ctrl := gomock.NewController(t)
   527  	sm := testdb.NewMockStateManager(ctrl)
   528  	p := NewProtocol(genesis.Default.Rewarding)
   529  	chainCtx := genesis.WithGenesisContext(
   530  		context.Background(),
   531  		genesis.Genesis{
   532  			Blockchain: genesis.Blockchain{GreenlandBlockHeight: 3},
   533  		},
   534  	)
   535  	ctx := protocol.WithBlockCtx(chainCtx, protocol.BlockCtx{
   536  		BlockHeight: 2,
   537  	})
   538  	ctx = protocol.WithFeatureCtx(ctx)
   539  
   540  	tests := []struct {
   541  		before, add *big.Int
   542  		v1          [2]bool
   543  	}{
   544  		{
   545  			big.NewInt(100), big.NewInt(20), [2]bool{true, true},
   546  		},
   547  		{
   548  			big.NewInt(120), big.NewInt(30), [2]bool{true, false},
   549  		},
   550  	}
   551  
   552  	// put V1 value
   553  	addr := identityset.Address(1)
   554  	acc := rewardAccount{
   555  		balance: tests[0].before,
   556  	}
   557  	accKey := append(_adminKey, addr.Bytes()...)
   558  	require.NoError(p.putState(ctx, sm, accKey, &acc))
   559  
   560  	for useV2 := 0; useV2 < 2; useV2++ {
   561  		if useV2 == 0 {
   562  			require.False(useV2Storage(ctx))
   563  		} else {
   564  			require.True(useV2Storage(ctx))
   565  		}
   566  		for i := 0; i < 2; i++ {
   567  			_, v1, err := p.stateCheckLegacy(ctx, sm, accKey, &acc)
   568  			require.Equal(tests[useV2].v1[i], v1)
   569  			require.NoError(err)
   570  			if i == 0 {
   571  				require.Equal(tests[useV2].before, acc.balance)
   572  			} else {
   573  				require.Equal(tests[useV2].before.Add(tests[useV2].before, tests[useV2].add), acc.balance)
   574  			}
   575  			if i == 0 {
   576  				// grant to existing addr
   577  				require.NoError(p.grantToAccount(ctx, sm, addr, tests[useV2].add))
   578  				// grant to new addr
   579  				newAddr := identityset.Address(4 + useV2)
   580  				require.NoError(p.grantToAccount(ctx, sm, newAddr, tests[useV2].add))
   581  				_, err = p.state(ctx, sm, append(_adminKey, newAddr.Bytes()...), &acc)
   582  				require.NoError(err)
   583  				require.Equal(acc.balance, tests[useV2].add)
   584  			}
   585  		}
   586  
   587  		if useV2 == 0 {
   588  			// switch to test V2
   589  			ctx = protocol.WithBlockCtx(chainCtx, protocol.BlockCtx{
   590  				BlockHeight: 3,
   591  			})
   592  			ctx = protocol.WithFeatureCtx(ctx)
   593  		}
   594  	}
   595  
   596  	// verify V1 deleted
   597  	_, err := p.stateV1(sm, accKey, &acc)
   598  	require.Equal(state.ErrStateNotExist, err)
   599  	_, err = p.stateV2(sm, accKey, &acc)
   600  	require.NoError(err)
   601  	require.Equal(tests[1].before, acc.balance)
   602  }
   603  
   604  func TestMigrateValue(t *testing.T) {
   605  	r := require.New(t)
   606  
   607  	a1 := admin{
   608  		blockReward:                    big.NewInt(10),
   609  		epochReward:                    big.NewInt(100),
   610  		numDelegatesForEpochReward:     10,
   611  		foundationBonus:                big.NewInt(5),
   612  		numDelegatesForFoundationBonus: 5,
   613  		foundationBonusLastEpoch:       365,
   614  		productivityThreshold:          50,
   615  	}
   616  	f1 := fund{
   617  		totalBalance:     new(big.Int),
   618  		unclaimedBalance: new(big.Int),
   619  	}
   620  	e1 := exempt{
   621  		[]address.Address{identityset.Address(31)},
   622  	}
   623  	g := genesis.Default
   624  
   625  	testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
   626  		// verify v1 state
   627  		a := admin{}
   628  		_, err := p.stateV1(sm, _adminKey, &a)
   629  		r.NoError(err)
   630  		r.Equal(a1, a)
   631  
   632  		f := fund{}
   633  		_, err = p.stateV1(sm, _fundKey, &f)
   634  		r.NoError(err)
   635  		r.Equal(f1, f)
   636  
   637  		e := exempt{}
   638  		_, err = p.stateV1(sm, _exemptKey, &e)
   639  		r.NoError(err)
   640  		r.Equal(e1, e)
   641  
   642  		// use numSubEpochs = 15
   643  		rp := rolldpos.NewProtocol(
   644  			g.NumCandidateDelegates,
   645  			g.NumDelegates,
   646  			15,
   647  			rolldpos.EnableDardanellesSubEpoch(g.DardanellesBlockHeight, g.DardanellesNumSubEpochs),
   648  		)
   649  		reg, ok := protocol.GetRegistry(ctx)
   650  		r.True(ok)
   651  		r.NoError(rp.ForceRegister(reg))
   652  
   653  		for _, v := range []struct {
   654  			height, lastEpoch uint64
   655  		}{
   656  			{g.GreenlandBlockHeight, a1.foundationBonusLastEpoch},
   657  			{1641601, g.FoundationBonusP2EndEpoch},
   658  			{g.KamchatkaBlockHeight, 30473},
   659  		} {
   660  			fCtx := ctx
   661  			if v.height == 1641601 {
   662  				// test the case where newStartEpoch < cfg.FoundationBonusP2StartEpoch
   663  				g1 := g
   664  				g1.GreenlandBlockHeight = v.height - 720
   665  				g1.KamchatkaBlockHeight = v.height
   666  				fCtx = genesis.WithGenesisContext(ctx, g1)
   667  			}
   668  			blkCtx := protocol.MustGetBlockCtx(ctx)
   669  			blkCtx.BlockHeight = v.height
   670  			fCtx = protocol.WithFeatureCtx(protocol.WithBlockCtx(fCtx, blkCtx))
   671  			r.NoError(p.CreatePreStates(fCtx, sm))
   672  
   673  			// verify v1 is deleted
   674  			_, err = p.stateV1(sm, _adminKey, &a)
   675  			r.Equal(state.ErrStateNotExist, err)
   676  			_, err = p.stateV1(sm, _fundKey, &f)
   677  			r.Equal(state.ErrStateNotExist, err)
   678  			_, err = p.stateV1(sm, _exemptKey, &e)
   679  			r.Equal(state.ErrStateNotExist, err)
   680  
   681  			// verify v2 exist
   682  			_, err = p.stateV2(sm, _adminKey, &a)
   683  			r.NoError(err)
   684  			_, err = p.stateV2(sm, _fundKey, &f)
   685  			r.NoError(err)
   686  			r.Equal(f1, f)
   687  			_, err = p.stateV2(sm, _exemptKey, &e)
   688  			r.NoError(err)
   689  			r.Equal(e1, e)
   690  
   691  			switch v.height {
   692  			case g.GreenlandBlockHeight:
   693  				r.Equal(a1, a)
   694  				// test migrate with no data
   695  				r.NoError(p.migrateValueGreenland(ctx, sm))
   696  			default:
   697  				r.Equal(v.lastEpoch, a.foundationBonusLastEpoch)
   698  				r.True(a.grantFoundationBonus(v.lastEpoch))
   699  				r.False(a.grantFoundationBonus(v.lastEpoch + 1))
   700  				a.foundationBonusLastEpoch = a1.foundationBonusLastEpoch
   701  				r.Equal(a1, a)
   702  				a.foundationBonusLastEpoch = v.lastEpoch
   703  				// test migrate with no data
   704  				r.NoError(p.migrateValueGreenland(ctx, sm))
   705  			}
   706  		}
   707  	}, true)
   708  }