github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/rewarding/reward_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  	"time"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	"github.com/iotexproject/iotex-address/address"
    16  	"github.com/iotexproject/iotex-election/test/mock/mock_committee"
    17  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"google.golang.org/protobuf/proto"
    21  
    22  	"github.com/iotexproject/iotex-core/action/protocol"
    23  	"github.com/iotexproject/iotex-core/action/protocol/account"
    24  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    25  	"github.com/iotexproject/iotex-core/action/protocol/poll"
    26  	"github.com/iotexproject/iotex-core/action/protocol/rewarding/rewardingpb"
    27  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    28  	"github.com/iotexproject/iotex-core/blockchain"
    29  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    30  	"github.com/iotexproject/iotex-core/db/batch"
    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/test/mock/mock_chainmanager"
    35  )
    36  
    37  func TestProtocol_GrantBlockReward(t *testing.T) {
    38  	testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
    39  		blkCtx, ok := protocol.GetBlockCtx(ctx)
    40  		require.True(t, ok)
    41  
    42  		// Grant block reward will fail because of no available balance
    43  		_, err := p.GrantBlockReward(ctx, sm)
    44  		require.Error(t, err)
    45  
    46  		_, err = p.Deposit(ctx, sm, big.NewInt(200), iotextypes.TransactionLogType_DEPOSIT_TO_REWARDING_FUND)
    47  		require.NoError(t, err)
    48  
    49  		// Grant block reward
    50  		rewardLog, err := p.GrantBlockReward(ctx, sm)
    51  		require.NoError(t, err)
    52  		require.Equal(t, p.addr.String(), rewardLog.Address)
    53  		var rl rewardingpb.RewardLog
    54  		require.NoError(t, proto.Unmarshal(rewardLog.Data, &rl))
    55  		require.Equal(t, rewardingpb.RewardLog_BLOCK_REWARD, rl.Type)
    56  		require.Equal(t, "10", rl.Amount)
    57  
    58  		availableBalance, _, err := p.AvailableBalance(ctx, sm)
    59  		require.NoError(t, err)
    60  		assert.Equal(t, big.NewInt(190), availableBalance)
    61  		// Operator shouldn't get reward
    62  		unclaimedBalance, _, err := p.UnclaimedBalance(ctx, sm, blkCtx.Producer)
    63  		require.NoError(t, err)
    64  		assert.Equal(t, big.NewInt(0), unclaimedBalance)
    65  		// Beneficiary should get reward
    66  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(0))
    67  		require.NoError(t, err)
    68  		assert.Equal(t, big.NewInt(10), unclaimedBalance)
    69  
    70  		// Grant the same block reward again will fail
    71  		_, err = p.GrantBlockReward(ctx, sm)
    72  		require.Error(t, err)
    73  	}, false)
    74  }
    75  
    76  func TestProtocol_GrantEpochReward(t *testing.T) {
    77  	testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
    78  		blkCtx, ok := protocol.GetBlockCtx(ctx)
    79  		require.True(t, ok)
    80  
    81  		_, err := p.Deposit(ctx, sm, big.NewInt(200), iotextypes.TransactionLogType_DEPOSIT_TO_REWARDING_FUND)
    82  		require.NoError(t, err)
    83  
    84  		ctx = protocol.WithFeatureWithHeightCtx(ctx)
    85  		// Grant epoch reward
    86  		rewardLogs, err := p.GrantEpochReward(ctx, sm)
    87  		require.NoError(t, err)
    88  		require.Equal(t, 8, len(rewardLogs))
    89  
    90  		availableBalance, _, err := p.AvailableBalance(ctx, sm)
    91  		require.NoError(t, err)
    92  		assert.Equal(t, big.NewInt(90+5), availableBalance)
    93  		// Operator shouldn't get reward
    94  		unclaimedBalance, _, err := p.UnclaimedBalance(ctx, sm, identityset.Address(27))
    95  		require.NoError(t, err)
    96  		assert.Equal(t, big.NewInt(0), unclaimedBalance)
    97  		// Beneficiary should get reward
    98  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(0))
    99  		require.NoError(t, err)
   100  		assert.Equal(t, big.NewInt(40+5), unclaimedBalance)
   101  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(28))
   102  		require.NoError(t, err)
   103  		assert.Equal(t, big.NewInt(30+5), unclaimedBalance)
   104  		// The 3-th candidate can't get the reward because it doesn't meet the productivity requirement
   105  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(29))
   106  		require.NoError(t, err)
   107  		assert.Equal(t, big.NewInt(5), unclaimedBalance)
   108  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(30))
   109  		require.NoError(t, err)
   110  		assert.Equal(t, big.NewInt(10+5), unclaimedBalance)
   111  		// The 5-th candidate can't get the reward because of being out of the range
   112  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(31))
   113  		require.NoError(t, err)
   114  		assert.Equal(t, big.NewInt(5), unclaimedBalance)
   115  		// The 6-th candidate can't get the foundation bonus because of being out of the range
   116  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(32))
   117  		require.NoError(t, err)
   118  		assert.Equal(t, big.NewInt(0), unclaimedBalance)
   119  
   120  		// Assert logs
   121  		expectedResults := []struct {
   122  			t      rewardingpb.RewardLog_RewardType
   123  			addr   string
   124  			amount string
   125  		}{
   126  			{
   127  				rewardingpb.RewardLog_EPOCH_REWARD,
   128  				identityset.Address(0).String(),
   129  				"40",
   130  			},
   131  			{
   132  				rewardingpb.RewardLog_EPOCH_REWARD,
   133  				identityset.Address(28).String(),
   134  				"30",
   135  			},
   136  			{
   137  				rewardingpb.RewardLog_EPOCH_REWARD,
   138  				identityset.Address(30).String(),
   139  				"10",
   140  			},
   141  			{
   142  				rewardingpb.RewardLog_FOUNDATION_BONUS,
   143  				identityset.Address(0).String(),
   144  				"5",
   145  			},
   146  			{
   147  				rewardingpb.RewardLog_FOUNDATION_BONUS,
   148  				identityset.Address(28).String(),
   149  				"5",
   150  			},
   151  			{
   152  				rewardingpb.RewardLog_FOUNDATION_BONUS,
   153  				identityset.Address(29).String(),
   154  				"5",
   155  			},
   156  			{
   157  				rewardingpb.RewardLog_FOUNDATION_BONUS,
   158  				identityset.Address(30).String(),
   159  				"5",
   160  			},
   161  			{
   162  				rewardingpb.RewardLog_FOUNDATION_BONUS,
   163  				identityset.Address(31).String(),
   164  				"5",
   165  			},
   166  		}
   167  		for i := 0; i < 8; i++ {
   168  			require.Equal(t, p.addr.String(), rewardLogs[i].Address)
   169  			var rl rewardingpb.RewardLog
   170  			require.NoError(t, proto.Unmarshal(rewardLogs[i].Data, &rl))
   171  			assert.Equal(t, expectedResults[i].t, rl.Type)
   172  			assert.Equal(t, expectedResults[i].addr, rl.Addr)
   173  			assert.Equal(t, expectedResults[i].amount, rl.Amount)
   174  		}
   175  
   176  		// Grant the same epoch reward again will fail
   177  		_, err = p.GrantEpochReward(ctx, sm)
   178  		require.Error(t, err)
   179  
   180  		// Grant the epoch reward on a block that is not the last one in an epoch will fail
   181  		blkCtx.BlockHeight++
   182  		ctx = protocol.WithBlockCtx(ctx, blkCtx)
   183  		_, err = p.GrantEpochReward(ctx, sm)
   184  		require.Error(t, err)
   185  	}, false)
   186  
   187  	testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
   188  		_, err := p.Deposit(ctx, sm, big.NewInt(200), iotextypes.TransactionLogType_DEPOSIT_TO_REWARDING_FUND)
   189  		require.NoError(t, err)
   190  
   191  		ctx = protocol.WithFeatureWithHeightCtx(ctx)
   192  		// Grant epoch reward
   193  		_, err = p.GrantEpochReward(ctx, sm)
   194  		require.NoError(t, err)
   195  
   196  		// The 5-th candidate can't get the reward because exempting from the epoch reward
   197  		unclaimedBalance, _, err := p.UnclaimedBalance(ctx, sm, identityset.Address(31))
   198  		require.NoError(t, err)
   199  		assert.Equal(t, big.NewInt(0), unclaimedBalance)
   200  		// The 6-th candidate can get the foundation bonus because it's still within the range after excluding 5-th one
   201  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(32))
   202  		require.NoError(t, err)
   203  		assert.Equal(t, big.NewInt(4+5), unclaimedBalance)
   204  	}, true)
   205  }
   206  
   207  func TestProtocol_ClaimReward(t *testing.T) {
   208  	testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
   209  		// Deposit 20 token into the rewarding fund
   210  		_, err := p.Deposit(ctx, sm, big.NewInt(20), iotextypes.TransactionLogType_DEPOSIT_TO_REWARDING_FUND)
   211  		require.NoError(t, err)
   212  
   213  		// Grant block reward
   214  		rewardLog, err := p.GrantBlockReward(ctx, sm)
   215  		require.NoError(t, err)
   216  		require.Equal(t, p.addr.String(), rewardLog.Address)
   217  		var rl rewardingpb.RewardLog
   218  		require.NoError(t, proto.Unmarshal(rewardLog.Data, &rl))
   219  		require.Equal(t, rewardingpb.RewardLog_BLOCK_REWARD, rl.Type)
   220  		require.Equal(t, "10", rl.Amount)
   221  
   222  		// Claim 5 token
   223  		actionCtx, ok := protocol.GetActionCtx(ctx)
   224  		require.True(t, ok)
   225  		claimActionCtx := actionCtx
   226  		claimActionCtx.Caller = identityset.Address(0)
   227  		claimCtx := protocol.WithActionCtx(ctx, claimActionCtx)
   228  
   229  		// Record the init balance of account
   230  		primAcc, err := accountutil.LoadAccount(sm, claimActionCtx.Caller)
   231  		require.NoError(t, err)
   232  		initBalance := primAcc.Balance
   233  
   234  		_, err = p.Claim(claimCtx, sm, big.NewInt(5))
   235  		require.NoError(t, err)
   236  
   237  		totalBalance, _, err := p.TotalBalance(ctx, sm)
   238  		require.NoError(t, err)
   239  		assert.Equal(t, big.NewInt(15), totalBalance)
   240  		unclaimedBalance, _, err := p.UnclaimedBalance(ctx, sm, claimActionCtx.Caller)
   241  		require.NoError(t, err)
   242  		assert.Equal(t, big.NewInt(5), unclaimedBalance)
   243  		primAcc, err = accountutil.LoadAccount(sm, claimActionCtx.Caller)
   244  		require.NoError(t, err)
   245  		initBalance = new(big.Int).Add(initBalance, big.NewInt(5))
   246  		assert.Equal(t, initBalance, primAcc.Balance)
   247  
   248  		// Claim negative amount of token will fail
   249  		_, err = p.Claim(claimCtx, sm, big.NewInt(-5))
   250  		require.Error(t, err)
   251  
   252  		// Claim 0 amount won't fail, but also will not get the token
   253  		_, err = p.Claim(claimCtx, sm, big.NewInt(0))
   254  		require.NoError(t, err)
   255  
   256  		totalBalance, _, err = p.TotalBalance(ctx, sm)
   257  		require.NoError(t, err)
   258  		assert.Equal(t, big.NewInt(15), totalBalance)
   259  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, claimActionCtx.Caller)
   260  		require.NoError(t, err)
   261  		assert.Equal(t, big.NewInt(5), unclaimedBalance)
   262  		primAcc, err = accountutil.LoadAccount(sm, claimActionCtx.Caller)
   263  		require.NoError(t, err)
   264  		assert.Equal(t, initBalance, primAcc.Balance)
   265  
   266  		// Claim another 5 token
   267  		rlog, err := p.Claim(claimCtx, sm, big.NewInt(5))
   268  		require.NoError(t, err)
   269  		require.NoError(t, err)
   270  		require.NotNil(t, rlog)
   271  		require.Equal(t, big.NewInt(5).String(), rlog.Amount.String())
   272  		require.Equal(t, address.RewardingPoolAddr, rlog.Sender)
   273  		require.Equal(t, claimActionCtx.Caller.String(), rlog.Recipient)
   274  
   275  		totalBalance, _, err = p.TotalBalance(ctx, sm)
   276  		require.NoError(t, err)
   277  		assert.Equal(t, big.NewInt(10), totalBalance)
   278  		unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, claimActionCtx.Caller)
   279  		require.NoError(t, err)
   280  		assert.Equal(t, big.NewInt(0), unclaimedBalance)
   281  		primAcc, err = accountutil.LoadAccount(sm, claimActionCtx.Caller)
   282  		require.NoError(t, err)
   283  		initBalance = new(big.Int).Add(initBalance, big.NewInt(5))
   284  		assert.Equal(t, initBalance, primAcc.Balance)
   285  
   286  		// Claim the 3-rd 5 token will fail be cause no balance for the address
   287  		_, err = p.Claim(claimCtx, sm, big.NewInt(5))
   288  		require.Error(t, err)
   289  
   290  		// Operator should have nothing to claim
   291  		blkCtx, ok := protocol.GetBlockCtx(ctx)
   292  		require.True(t, ok)
   293  		claimActionCtx.Caller = blkCtx.Producer
   294  		claimCtx = protocol.WithActionCtx(ctx, claimActionCtx)
   295  		_, err = p.Claim(claimCtx, sm, big.NewInt(1))
   296  		require.Error(t, err)
   297  	}, false)
   298  }
   299  
   300  func TestProtocol_NoRewardAddr(t *testing.T) {
   301  	ctrl := gomock.NewController(t)
   302  
   303  	registry := protocol.NewRegistry()
   304  	sm := mock_chainmanager.NewMockStateManager(ctrl)
   305  	cb := batch.NewCachedBatch()
   306  	sm.EXPECT().State(gomock.Any(), gomock.Any()).DoAndReturn(
   307  		func(account interface{}, opts ...protocol.StateOption) (uint64, error) {
   308  			cfg, err := protocol.CreateStateConfig(opts...)
   309  			if err != nil {
   310  				return 0, err
   311  			}
   312  			val, err := cb.Get("state", cfg.Key)
   313  			if err != nil {
   314  				return 0, state.ErrStateNotExist
   315  			}
   316  			return 0, state.Deserialize(account, val)
   317  		}).AnyTimes()
   318  	sm.EXPECT().PutState(gomock.Any(), gomock.Any()).DoAndReturn(
   319  		func(account interface{}, opts ...protocol.StateOption) (uint64, error) {
   320  			cfg, err := protocol.CreateStateConfig(opts...)
   321  			if err != nil {
   322  				return 0, err
   323  			}
   324  			ss, err := state.Serialize(account)
   325  			if err != nil {
   326  				return 0, err
   327  			}
   328  			cb.Put("state", cfg.Key, ss, "failed to put state")
   329  			return 0, nil
   330  		}).AnyTimes()
   331  	sm.EXPECT().Height().Return(uint64(1), nil).AnyTimes()
   332  
   333  	ge := genesis.Default
   334  	ge.Rewarding.InitBalanceStr = "0"
   335  	ge.Rewarding.BlockRewardStr = "10"
   336  	ge.Rewarding.EpochRewardStr = "100"
   337  	ge.Rewarding.NumDelegatesForEpochReward = 10
   338  	ge.Rewarding.ExemptAddrStrsFromEpochReward = []string{}
   339  	ge.Rewarding.FoundationBonusStr = "5"
   340  	ge.Rewarding.NumDelegatesForFoundationBonus = 5
   341  	ge.Rewarding.FoundationBonusLastEpoch = 365
   342  	ge.Rewarding.ProductivityThreshold = 50
   343  	ge.Rewarding.FoundationBonusP2StartEpoch = 365
   344  	ge.Rewarding.FoundationBonusP2EndEpoch = 365
   345  
   346  	// Create a test account with 1000 token
   347  	ge.InitBalanceMap[identityset.Address(0).String()] = "1000"
   348  
   349  	p := NewProtocol(ge.Rewarding)
   350  	rp := rolldpos.NewProtocol(
   351  		genesis.Default.NumCandidateDelegates,
   352  		genesis.Default.NumDelegates,
   353  		genesis.Default.NumSubEpochs,
   354  	)
   355  	abps := []*state.Candidate{
   356  		{
   357  			Address:       identityset.Address(0).String(),
   358  			Votes:         unit.ConvertIotxToRau(1000000),
   359  			RewardAddress: identityset.Address(0).String(),
   360  		},
   361  		{
   362  			Address:       identityset.Address(1).String(),
   363  			Votes:         unit.ConvertIotxToRau(1000000),
   364  			RewardAddress: identityset.Address(1).String(),
   365  		},
   366  	}
   367  	g := genesis.Default
   368  	committee := mock_committee.NewMockCommittee(ctrl)
   369  	slasher, err := poll.NewSlasher(
   370  		func(uint64, uint64) (map[string]uint64, error) {
   371  			return map[string]uint64{
   372  				identityset.Address(0).String(): 9,
   373  				identityset.Address(1).String(): 10,
   374  			}, nil
   375  		},
   376  		func(protocol.StateReader, uint64, bool, bool) ([]*state.Candidate, uint64, error) {
   377  			return abps, 0, nil
   378  		},
   379  		nil,
   380  		nil,
   381  		nil,
   382  		2,
   383  		2,
   384  		g.DardanellesNumSubEpochs,
   385  		g.ProductivityThreshold,
   386  		g.ProbationEpochPeriod,
   387  		g.UnproductiveDelegateMaxCacheSize,
   388  		g.ProbationIntensityRate)
   389  	require.NoError(t, err)
   390  	pp, err := poll.NewGovernanceChainCommitteeProtocol(
   391  		nil,
   392  		committee,
   393  		uint64(123456),
   394  		func(uint64) (time.Time, error) { return time.Now(), nil },
   395  		blockchain.DefaultConfig.PollInitialCandidatesInterval,
   396  		slasher,
   397  	)
   398  	require.NoError(t, err)
   399  	require.NoError(t, rp.Register(registry))
   400  	require.NoError(t, pp.Register(registry))
   401  	require.NoError(t, p.Register(registry))
   402  
   403  	// Initialize the protocol
   404  	ctx := protocol.WithBlockCtx(
   405  		genesis.WithGenesisContext(
   406  			protocol.WithRegistry(context.Background(), registry),
   407  			ge,
   408  		),
   409  		protocol.BlockCtx{
   410  			BlockHeight: 0,
   411  		},
   412  	)
   413  	ctx = protocol.WithFeatureCtx(ctx)
   414  	ap := account.NewProtocol(DepositGas)
   415  	require.NoError(t, ap.CreateGenesisStates(ctx, sm))
   416  	require.NoError(t, p.CreateGenesisStates(ctx, sm))
   417  	ctx = protocol.WithBlockchainCtx(
   418  		protocol.WithRegistry(ctx, registry),
   419  		protocol.BlockchainCtx{
   420  			Tip: protocol.TipInfo{
   421  				Height: 1,
   422  			},
   423  		},
   424  	)
   425  
   426  	require.NoError(t, p.CreateGenesisStates(ctx, sm))
   427  
   428  	ctx = protocol.WithBlockCtx(
   429  		ctx,
   430  		protocol.BlockCtx{
   431  			Producer:    identityset.Address(0),
   432  			BlockHeight: genesis.Default.NumDelegates * genesis.Default.NumSubEpochs,
   433  		},
   434  	)
   435  	ctx = protocol.WithActionCtx(
   436  		ctx,
   437  		protocol.ActionCtx{
   438  			Caller: identityset.Address(0),
   439  		},
   440  	)
   441  	_, err = p.Deposit(ctx, sm, big.NewInt(200), iotextypes.TransactionLogType_DEPOSIT_TO_REWARDING_FUND)
   442  	require.NoError(t, err)
   443  
   444  	ctx = protocol.WithFeatureWithHeightCtx(ctx)
   445  	// Grant block reward
   446  	_, err = p.GrantBlockReward(ctx, sm)
   447  	require.NoError(t, err)
   448  
   449  	availableBalance, _, err := p.AvailableBalance(ctx, sm)
   450  	require.NoError(t, err)
   451  	assert.Equal(t, big.NewInt(190), availableBalance)
   452  	unclaimedBalance, _, err := p.UnclaimedBalance(ctx, sm, identityset.Address(0))
   453  	require.NoError(t, err)
   454  	assert.Equal(t, big.NewInt(10), unclaimedBalance)
   455  
   456  	// Grant epoch reward
   457  	ctx = protocol.WithFeatureCtx(ctx)
   458  	rewardLogs, err := p.GrantEpochReward(ctx, sm)
   459  	require.NoError(t, err)
   460  	require.Equal(t, 4, len(rewardLogs))
   461  
   462  	availableBalance, _, err = p.AvailableBalance(ctx, sm)
   463  	require.NoError(t, err)
   464  	assert.Equal(t, big.NewInt(80), availableBalance)
   465  	unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(0))
   466  	require.NoError(t, err)
   467  	assert.Equal(t, big.NewInt(65), unclaimedBalance)
   468  	// It doesn't affect others to get reward
   469  	unclaimedBalance, _, err = p.UnclaimedBalance(ctx, sm, identityset.Address(1))
   470  	require.NoError(t, err)
   471  	assert.Equal(t, big.NewInt(55), unclaimedBalance)
   472  	require.Equal(t, p.addr.String(), rewardLogs[0].Address)
   473  	var rl rewardingpb.RewardLog
   474  	require.NoError(t, proto.Unmarshal(rewardLogs[0].Data, &rl))
   475  	assert.Equal(t, rewardingpb.RewardLog_EPOCH_REWARD, rl.Type)
   476  	assert.Equal(t, identityset.Address(0).String(), rl.Addr)
   477  	assert.Equal(t, "50", rl.Amount)
   478  	require.Equal(t, p.addr.String(), rewardLogs[1].Address)
   479  	require.NoError(t, proto.Unmarshal(rewardLogs[1].Data, &rl))
   480  	assert.Equal(t, rewardingpb.RewardLog_EPOCH_REWARD, rl.Type)
   481  	assert.Equal(t, identityset.Address(1).String(), rl.Addr)
   482  	assert.Equal(t, "50", rl.Amount)
   483  }