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 }