github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/executor/reward_validator_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package executor 5 6 import ( 7 "context" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/MetalBlockchain/metalgo/database" 14 "github.com/MetalBlockchain/metalgo/ids" 15 "github.com/MetalBlockchain/metalgo/snow/snowtest" 16 "github.com/MetalBlockchain/metalgo/utils/constants" 17 "github.com/MetalBlockchain/metalgo/utils/math" 18 "github.com/MetalBlockchain/metalgo/utils/set" 19 "github.com/MetalBlockchain/metalgo/vms/components/avax" 20 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 21 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 22 "github.com/MetalBlockchain/metalgo/vms/platformvm/status" 23 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 24 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 25 26 walletsigner "github.com/MetalBlockchain/metalgo/wallet/chain/p/signer" 27 ) 28 29 func newRewardValidatorTx(t testing.TB, txID ids.ID) (*txs.Tx, error) { 30 utx := &txs.RewardValidatorTx{TxID: txID} 31 tx, err := txs.NewSigned(utx, txs.Codec, nil) 32 if err != nil { 33 return nil, err 34 } 35 return tx, tx.SyntacticVerify(snowtest.Context(t, snowtest.PChainID)) 36 } 37 38 func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { 39 require := require.New(t) 40 env := newEnvironment(t, apricotPhase5) 41 dummyHeight := uint64(1) 42 43 currentStakerIterator, err := env.state.GetCurrentStakerIterator() 44 require.NoError(err) 45 require.True(currentStakerIterator.Next()) 46 47 stakerToRemove := currentStakerIterator.Value() 48 currentStakerIterator.Release() 49 50 stakerToRemoveTxIntf, _, err := env.state.GetTx(stakerToRemove.TxID) 51 require.NoError(err) 52 stakerToRemoveTx := stakerToRemoveTxIntf.Unsigned.(*txs.AddValidatorTx) 53 54 // Case 1: Chain timestamp is wrong 55 tx, err := newRewardValidatorTx(t, stakerToRemove.TxID) 56 require.NoError(err) 57 58 onCommitState, err := state.NewDiff(lastAcceptedID, env) 59 require.NoError(err) 60 61 onAbortState, err := state.NewDiff(lastAcceptedID, env) 62 require.NoError(err) 63 64 txExecutor := ProposalTxExecutor{ 65 OnCommitState: onCommitState, 66 OnAbortState: onAbortState, 67 Backend: &env.backend, 68 Tx: tx, 69 } 70 err = tx.Unsigned.Visit(&txExecutor) 71 require.ErrorIs(err, ErrRemoveStakerTooEarly) 72 73 // Advance chain timestamp to time that next validator leaves 74 env.state.SetTimestamp(stakerToRemove.EndTime) 75 76 // Case 2: Wrong validator 77 tx, err = newRewardValidatorTx(t, ids.GenerateTestID()) 78 require.NoError(err) 79 80 onCommitState, err = state.NewDiff(lastAcceptedID, env) 81 require.NoError(err) 82 83 onAbortState, err = state.NewDiff(lastAcceptedID, env) 84 require.NoError(err) 85 86 txExecutor = ProposalTxExecutor{ 87 OnCommitState: onCommitState, 88 OnAbortState: onAbortState, 89 Backend: &env.backend, 90 Tx: tx, 91 } 92 err = tx.Unsigned.Visit(&txExecutor) 93 require.ErrorIs(err, ErrRemoveWrongStaker) 94 95 // Case 3: Happy path 96 tx, err = newRewardValidatorTx(t, stakerToRemove.TxID) 97 require.NoError(err) 98 99 onCommitState, err = state.NewDiff(lastAcceptedID, env) 100 require.NoError(err) 101 102 onAbortState, err = state.NewDiff(lastAcceptedID, env) 103 require.NoError(err) 104 105 txExecutor = ProposalTxExecutor{ 106 OnCommitState: onCommitState, 107 OnAbortState: onAbortState, 108 Backend: &env.backend, 109 Tx: tx, 110 } 111 require.NoError(tx.Unsigned.Visit(&txExecutor)) 112 113 onCommitStakerIterator, err := txExecutor.OnCommitState.GetCurrentStakerIterator() 114 require.NoError(err) 115 require.True(onCommitStakerIterator.Next()) 116 117 nextToRemove := onCommitStakerIterator.Value() 118 onCommitStakerIterator.Release() 119 require.NotEqual(stakerToRemove.TxID, nextToRemove.TxID) 120 121 // check that stake/reward is given back 122 stakeOwners := stakerToRemoveTx.StakeOuts[0].Out.(*secp256k1fx.TransferOutput).AddressesSet() 123 124 // Get old balances 125 oldBalance, err := avax.GetBalance(env.state, stakeOwners) 126 require.NoError(err) 127 128 require.NoError(txExecutor.OnCommitState.Apply(env.state)) 129 130 env.state.SetHeight(dummyHeight) 131 require.NoError(env.state.Commit()) 132 133 onCommitBalance, err := avax.GetBalance(env.state, stakeOwners) 134 require.NoError(err) 135 require.Equal(oldBalance+stakerToRemove.Weight+27697, onCommitBalance) 136 } 137 138 func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { 139 require := require.New(t) 140 env := newEnvironment(t, apricotPhase5) 141 dummyHeight := uint64(1) 142 143 currentStakerIterator, err := env.state.GetCurrentStakerIterator() 144 require.NoError(err) 145 require.True(currentStakerIterator.Next()) 146 147 stakerToRemove := currentStakerIterator.Value() 148 currentStakerIterator.Release() 149 150 stakerToRemoveTxIntf, _, err := env.state.GetTx(stakerToRemove.TxID) 151 require.NoError(err) 152 stakerToRemoveTx := stakerToRemoveTxIntf.Unsigned.(*txs.AddValidatorTx) 153 154 // Case 1: Chain timestamp is wrong 155 tx, err := newRewardValidatorTx(t, stakerToRemove.TxID) 156 require.NoError(err) 157 158 onCommitState, err := state.NewDiff(lastAcceptedID, env) 159 require.NoError(err) 160 161 onAbortState, err := state.NewDiff(lastAcceptedID, env) 162 require.NoError(err) 163 164 txExecutor := ProposalTxExecutor{ 165 OnCommitState: onCommitState, 166 OnAbortState: onAbortState, 167 Backend: &env.backend, 168 Tx: tx, 169 } 170 err = tx.Unsigned.Visit(&txExecutor) 171 require.ErrorIs(err, ErrRemoveStakerTooEarly) 172 173 // Advance chain timestamp to time that next validator leaves 174 env.state.SetTimestamp(stakerToRemove.EndTime) 175 176 // Case 2: Wrong validator 177 tx, err = newRewardValidatorTx(t, ids.GenerateTestID()) 178 require.NoError(err) 179 180 txExecutor = ProposalTxExecutor{ 181 OnCommitState: onCommitState, 182 OnAbortState: onAbortState, 183 Backend: &env.backend, 184 Tx: tx, 185 } 186 err = tx.Unsigned.Visit(&txExecutor) 187 require.ErrorIs(err, ErrRemoveWrongStaker) 188 189 // Case 3: Happy path 190 tx, err = newRewardValidatorTx(t, stakerToRemove.TxID) 191 require.NoError(err) 192 193 onCommitState, err = state.NewDiff(lastAcceptedID, env) 194 require.NoError(err) 195 196 onAbortState, err = state.NewDiff(lastAcceptedID, env) 197 require.NoError(err) 198 199 txExecutor = ProposalTxExecutor{ 200 OnCommitState: onCommitState, 201 OnAbortState: onAbortState, 202 Backend: &env.backend, 203 Tx: tx, 204 } 205 require.NoError(tx.Unsigned.Visit(&txExecutor)) 206 207 onAbortStakerIterator, err := txExecutor.OnAbortState.GetCurrentStakerIterator() 208 require.NoError(err) 209 require.True(onAbortStakerIterator.Next()) 210 211 nextToRemove := onAbortStakerIterator.Value() 212 onAbortStakerIterator.Release() 213 require.NotEqual(stakerToRemove.TxID, nextToRemove.TxID) 214 215 // check that stake/reward isn't given back 216 stakeOwners := stakerToRemoveTx.StakeOuts[0].Out.(*secp256k1fx.TransferOutput).AddressesSet() 217 218 // Get old balances 219 oldBalance, err := avax.GetBalance(env.state, stakeOwners) 220 require.NoError(err) 221 222 require.NoError(txExecutor.OnAbortState.Apply(env.state)) 223 224 env.state.SetHeight(dummyHeight) 225 require.NoError(env.state.Commit()) 226 227 onAbortBalance, err := avax.GetBalance(env.state, stakeOwners) 228 require.NoError(err) 229 require.Equal(oldBalance+stakerToRemove.Weight, onAbortBalance) 230 } 231 232 func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { 233 require := require.New(t) 234 env := newEnvironment(t, apricotPhase5) 235 dummyHeight := uint64(1) 236 237 vdrRewardAddress := ids.GenerateTestShortID() 238 delRewardAddress := ids.GenerateTestShortID() 239 240 vdrStartTime := uint64(defaultValidateStartTime.Unix()) + 1 241 vdrEndTime := uint64(defaultValidateStartTime.Add(2 * defaultMinStakingDuration).Unix()) 242 vdrNodeID := ids.GenerateTestNodeID() 243 244 builder, signer := env.factory.NewWallet(preFundedKeys[0]) 245 uVdrTx, err := builder.NewAddValidatorTx( 246 &txs.Validator{ 247 NodeID: vdrNodeID, 248 Start: vdrStartTime, 249 End: vdrEndTime, 250 Wght: env.config.MinValidatorStake, 251 }, 252 &secp256k1fx.OutputOwners{ 253 Threshold: 1, 254 Addrs: []ids.ShortID{vdrRewardAddress}, 255 }, 256 reward.PercentDenominator/4, 257 ) 258 require.NoError(err) 259 vdrTx, err := walletsigner.SignUnsigned(context.Background(), signer, uVdrTx) 260 require.NoError(err) 261 262 delStartTime := vdrStartTime 263 delEndTime := vdrEndTime 264 265 uDelTx, err := builder.NewAddDelegatorTx( 266 &txs.Validator{ 267 NodeID: vdrNodeID, 268 Start: delStartTime, 269 End: delEndTime, 270 Wght: env.config.MinDelegatorStake, 271 }, 272 &secp256k1fx.OutputOwners{ 273 Threshold: 1, 274 Addrs: []ids.ShortID{delRewardAddress}, 275 }, 276 ) 277 require.NoError(err) 278 delTx, err := walletsigner.SignUnsigned(context.Background(), signer, uDelTx) 279 require.NoError(err) 280 281 addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) 282 vdrStaker, err := state.NewCurrentStaker( 283 vdrTx.ID(), 284 addValTx, 285 addValTx.StartTime(), 286 0, 287 ) 288 require.NoError(err) 289 290 addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) 291 delStaker, err := state.NewCurrentStaker( 292 delTx.ID(), 293 addDelTx, 294 addDelTx.StartTime(), 295 1000000, 296 ) 297 require.NoError(err) 298 299 env.state.PutCurrentValidator(vdrStaker) 300 env.state.AddTx(vdrTx, status.Committed) 301 env.state.PutCurrentDelegator(delStaker) 302 env.state.AddTx(delTx, status.Committed) 303 env.state.SetTimestamp(time.Unix(int64(delEndTime), 0)) 304 env.state.SetHeight(dummyHeight) 305 require.NoError(env.state.Commit()) 306 307 // test validator stake 308 stake := env.config.Validators.GetWeight(constants.PrimaryNetworkID, vdrNodeID) 309 require.Equal(env.config.MinValidatorStake+env.config.MinDelegatorStake, stake) 310 311 tx, err := newRewardValidatorTx(t, delTx.ID()) 312 require.NoError(err) 313 314 onCommitState, err := state.NewDiff(lastAcceptedID, env) 315 require.NoError(err) 316 317 onAbortState, err := state.NewDiff(lastAcceptedID, env) 318 require.NoError(err) 319 320 txExecutor := ProposalTxExecutor{ 321 OnCommitState: onCommitState, 322 OnAbortState: onAbortState, 323 Backend: &env.backend, 324 Tx: tx, 325 } 326 require.NoError(tx.Unsigned.Visit(&txExecutor)) 327 328 vdrDestSet := set.Of(vdrRewardAddress) 329 delDestSet := set.Of(delRewardAddress) 330 331 expectedReward := uint64(1000000) 332 333 oldVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 334 require.NoError(err) 335 oldDelBalance, err := avax.GetBalance(env.state, delDestSet) 336 require.NoError(err) 337 338 require.NoError(txExecutor.OnCommitState.Apply(env.state)) 339 340 env.state.SetHeight(dummyHeight) 341 require.NoError(env.state.Commit()) 342 343 // Since the tx was committed, the delegator and the delegatee should be rewarded. 344 // The delegator reward should be higher since the delegatee's share is 25%. 345 commitVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 346 require.NoError(err) 347 vdrReward, err := math.Sub(commitVdrBalance, oldVdrBalance) 348 require.NoError(err) 349 require.NotZero(vdrReward, "expected delegatee balance to increase because of reward") 350 351 commitDelBalance, err := avax.GetBalance(env.state, delDestSet) 352 require.NoError(err) 353 delReward, err := math.Sub(commitDelBalance, oldDelBalance) 354 require.NoError(err) 355 require.NotZero(delReward, "expected delegator balance to increase because of reward") 356 357 require.Less(vdrReward, delReward, "the delegator's reward should be greater than the delegatee's because the delegatee's share is 25%") 358 require.Equal(expectedReward, delReward+vdrReward, "expected total reward to be %d but is %d", expectedReward, delReward+vdrReward) 359 360 stake = env.config.Validators.GetWeight(constants.PrimaryNetworkID, vdrNodeID) 361 require.Equal(env.config.MinValidatorStake, stake) 362 } 363 364 func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { 365 require := require.New(t) 366 env := newEnvironment(t, cortina) 367 dummyHeight := uint64(1) 368 369 vdrRewardAddress := ids.GenerateTestShortID() 370 delRewardAddress := ids.GenerateTestShortID() 371 372 vdrStartTime := uint64(defaultValidateStartTime.Unix()) + 1 373 vdrEndTime := uint64(defaultValidateStartTime.Add(2 * defaultMinStakingDuration).Unix()) 374 vdrNodeID := ids.GenerateTestNodeID() 375 376 builder, signer := env.factory.NewWallet(preFundedKeys[0]) 377 uVdrTx, err := builder.NewAddValidatorTx( 378 &txs.Validator{ 379 NodeID: vdrNodeID, 380 Start: vdrStartTime, 381 End: vdrEndTime, 382 Wght: env.config.MinValidatorStake, 383 }, 384 &secp256k1fx.OutputOwners{ 385 Threshold: 1, 386 Addrs: []ids.ShortID{vdrRewardAddress}, 387 }, 388 reward.PercentDenominator/4, 389 ) 390 require.NoError(err) 391 vdrTx, err := walletsigner.SignUnsigned(context.Background(), signer, uVdrTx) 392 require.NoError(err) 393 394 delStartTime := vdrStartTime 395 delEndTime := vdrEndTime 396 397 uDelTx, err := builder.NewAddDelegatorTx( 398 &txs.Validator{ 399 NodeID: vdrNodeID, 400 Start: delStartTime, 401 End: delEndTime, 402 Wght: env.config.MinDelegatorStake, 403 }, 404 &secp256k1fx.OutputOwners{ 405 Threshold: 1, 406 Addrs: []ids.ShortID{delRewardAddress}, 407 }, 408 ) 409 require.NoError(err) 410 delTx, err := walletsigner.SignUnsigned(context.Background(), signer, uDelTx) 411 require.NoError(err) 412 413 addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) 414 vdrRewardAmt := uint64(2000000) 415 vdrStaker, err := state.NewCurrentStaker( 416 vdrTx.ID(), 417 addValTx, 418 time.Unix(int64(vdrStartTime), 0), 419 vdrRewardAmt, 420 ) 421 require.NoError(err) 422 423 addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) 424 delRewardAmt := uint64(1000000) 425 delStaker, err := state.NewCurrentStaker( 426 delTx.ID(), 427 addDelTx, 428 time.Unix(int64(delStartTime), 0), 429 delRewardAmt, 430 ) 431 require.NoError(err) 432 433 env.state.PutCurrentValidator(vdrStaker) 434 env.state.AddTx(vdrTx, status.Committed) 435 env.state.PutCurrentDelegator(delStaker) 436 env.state.AddTx(delTx, status.Committed) 437 env.state.SetTimestamp(time.Unix(int64(vdrEndTime), 0)) 438 env.state.SetHeight(dummyHeight) 439 require.NoError(env.state.Commit()) 440 441 vdrDestSet := set.Of(vdrRewardAddress) 442 delDestSet := set.Of(delRewardAddress) 443 444 oldVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 445 require.NoError(err) 446 oldDelBalance, err := avax.GetBalance(env.state, delDestSet) 447 require.NoError(err) 448 449 // test validator stake 450 stake := env.config.Validators.GetWeight(constants.PrimaryNetworkID, vdrNodeID) 451 require.Equal(env.config.MinValidatorStake+env.config.MinDelegatorStake, stake) 452 453 tx, err := newRewardValidatorTx(t, delTx.ID()) 454 require.NoError(err) 455 456 // Create Delegator Diff 457 onCommitState, err := state.NewDiff(lastAcceptedID, env) 458 require.NoError(err) 459 460 onAbortState, err := state.NewDiff(lastAcceptedID, env) 461 require.NoError(err) 462 463 txExecutor := ProposalTxExecutor{ 464 OnCommitState: onCommitState, 465 OnAbortState: onAbortState, 466 Backend: &env.backend, 467 Tx: tx, 468 } 469 require.NoError(tx.Unsigned.Visit(&txExecutor)) 470 471 // The delegator should be rewarded if the ProposalTx is committed. Since the 472 // delegatee's share is 25%, we expect the delegator to receive 75% of the reward. 473 // Since this is post [CortinaTime], the delegatee should not be rewarded until a 474 // RewardValidatorTx is issued for the delegatee. 475 numDelStakeUTXOs := uint32(len(delTx.Unsigned.InputIDs())) 476 delRewardUTXOID := &avax.UTXOID{ 477 TxID: delTx.ID(), 478 OutputIndex: numDelStakeUTXOs + 1, 479 } 480 481 utxo, err := onCommitState.GetUTXO(delRewardUTXOID.InputID()) 482 require.NoError(err) 483 require.IsType(&secp256k1fx.TransferOutput{}, utxo.Out) 484 castUTXO := utxo.Out.(*secp256k1fx.TransferOutput) 485 require.Equal(delRewardAmt*3/4, castUTXO.Amt, "expected delegator balance to increase by 3/4 of reward amount") 486 require.True(delDestSet.Equals(castUTXO.AddressesSet()), "expected reward UTXO to be issued to delDestSet") 487 488 preCortinaVdrRewardUTXOID := &avax.UTXOID{ 489 TxID: delTx.ID(), 490 OutputIndex: numDelStakeUTXOs + 2, 491 } 492 _, err = onCommitState.GetUTXO(preCortinaVdrRewardUTXOID.InputID()) 493 require.ErrorIs(err, database.ErrNotFound) 494 495 // Commit Delegator Diff 496 require.NoError(txExecutor.OnCommitState.Apply(env.state)) 497 498 env.state.SetHeight(dummyHeight) 499 require.NoError(env.state.Commit()) 500 501 tx, err = newRewardValidatorTx(t, vdrStaker.TxID) 502 require.NoError(err) 503 504 // Create Validator Diff 505 onCommitState, err = state.NewDiff(lastAcceptedID, env) 506 require.NoError(err) 507 508 onAbortState, err = state.NewDiff(lastAcceptedID, env) 509 require.NoError(err) 510 511 txExecutor = ProposalTxExecutor{ 512 OnCommitState: onCommitState, 513 OnAbortState: onAbortState, 514 Backend: &env.backend, 515 Tx: tx, 516 } 517 require.NoError(tx.Unsigned.Visit(&txExecutor)) 518 519 require.NotEqual(vdrStaker.TxID, delStaker.TxID) 520 521 numVdrStakeUTXOs := uint32(len(delTx.Unsigned.InputIDs())) 522 523 // check for validator reward here 524 vdrRewardUTXOID := &avax.UTXOID{ 525 TxID: vdrTx.ID(), 526 OutputIndex: numVdrStakeUTXOs + 1, 527 } 528 529 utxo, err = onCommitState.GetUTXO(vdrRewardUTXOID.InputID()) 530 require.NoError(err) 531 require.IsType(&secp256k1fx.TransferOutput{}, utxo.Out) 532 castUTXO = utxo.Out.(*secp256k1fx.TransferOutput) 533 require.Equal(vdrRewardAmt, castUTXO.Amt, "expected validator to be rewarded") 534 require.True(vdrDestSet.Equals(castUTXO.AddressesSet()), "expected reward UTXO to be issued to vdrDestSet") 535 536 // check for validator's batched delegator rewards here 537 onCommitVdrDelRewardUTXOID := &avax.UTXOID{ 538 TxID: vdrTx.ID(), 539 OutputIndex: numVdrStakeUTXOs + 2, 540 } 541 542 utxo, err = onCommitState.GetUTXO(onCommitVdrDelRewardUTXOID.InputID()) 543 require.NoError(err) 544 require.IsType(&secp256k1fx.TransferOutput{}, utxo.Out) 545 castUTXO = utxo.Out.(*secp256k1fx.TransferOutput) 546 require.Equal(delRewardAmt/4, castUTXO.Amt, "expected validator to be rewarded with accrued delegator rewards") 547 require.True(vdrDestSet.Equals(castUTXO.AddressesSet()), "expected reward UTXO to be issued to vdrDestSet") 548 549 // aborted validator tx should still distribute accrued delegator rewards 550 onAbortVdrDelRewardUTXOID := &avax.UTXOID{ 551 TxID: vdrTx.ID(), 552 OutputIndex: numVdrStakeUTXOs + 1, 553 } 554 555 utxo, err = onAbortState.GetUTXO(onAbortVdrDelRewardUTXOID.InputID()) 556 require.NoError(err) 557 require.IsType(&secp256k1fx.TransferOutput{}, utxo.Out) 558 castUTXO = utxo.Out.(*secp256k1fx.TransferOutput) 559 require.Equal(delRewardAmt/4, castUTXO.Amt, "expected validator to be rewarded with accrued delegator rewards") 560 require.True(vdrDestSet.Equals(castUTXO.AddressesSet()), "expected reward UTXO to be issued to vdrDestSet") 561 562 _, err = onCommitState.GetUTXO(preCortinaVdrRewardUTXOID.InputID()) 563 require.ErrorIs(err, database.ErrNotFound) 564 565 // Commit Validator Diff 566 require.NoError(txExecutor.OnCommitState.Apply(env.state)) 567 568 env.state.SetHeight(dummyHeight) 569 require.NoError(env.state.Commit()) 570 571 // Since the tx was committed, the delegator and the delegatee should be rewarded. 572 // The delegator reward should be higher since the delegatee's share is 25%. 573 commitVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 574 require.NoError(err) 575 vdrReward, err := math.Sub(commitVdrBalance, oldVdrBalance) 576 require.NoError(err) 577 delegateeReward, err := math.Sub(vdrReward, 2000000) 578 require.NoError(err) 579 require.NotZero(delegateeReward, "expected delegatee balance to increase because of reward") 580 581 commitDelBalance, err := avax.GetBalance(env.state, delDestSet) 582 require.NoError(err) 583 delReward, err := math.Sub(commitDelBalance, oldDelBalance) 584 require.NoError(err) 585 require.NotZero(delReward, "expected delegator balance to increase because of reward") 586 587 require.Less(delegateeReward, delReward, "the delegator's reward should be greater than the delegatee's because the delegatee's share is 25%") 588 require.Equal(delRewardAmt, delReward+delegateeReward, "expected total reward to be %d but is %d", delRewardAmt, delReward+vdrReward) 589 } 590 591 func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { 592 require := require.New(t) 593 env := newEnvironment(t, cortina) 594 dummyHeight := uint64(1) 595 596 vdrRewardAddress := ids.GenerateTestShortID() 597 delRewardAddress := ids.GenerateTestShortID() 598 599 vdrStartTime := uint64(defaultValidateStartTime.Unix()) + 1 600 vdrEndTime := uint64(defaultValidateStartTime.Add(2 * defaultMinStakingDuration).Unix()) 601 vdrNodeID := ids.GenerateTestNodeID() 602 603 builder, signer := env.factory.NewWallet(preFundedKeys[0]) 604 uVdrTx, err := builder.NewAddValidatorTx( 605 &txs.Validator{ 606 NodeID: vdrNodeID, 607 Start: vdrStartTime, 608 End: vdrEndTime, 609 Wght: env.config.MinValidatorStake, 610 }, 611 &secp256k1fx.OutputOwners{ 612 Threshold: 1, 613 Addrs: []ids.ShortID{vdrRewardAddress}, 614 }, 615 reward.PercentDenominator/4, 616 ) 617 require.NoError(err) 618 vdrTx, err := walletsigner.SignUnsigned(context.Background(), signer, uVdrTx) 619 require.NoError(err) 620 621 delStartTime := vdrStartTime 622 delEndTime := vdrEndTime 623 624 uDelTx, err := builder.NewAddDelegatorTx( 625 &txs.Validator{ 626 NodeID: vdrNodeID, 627 Start: delStartTime, 628 End: delEndTime, 629 Wght: env.config.MinDelegatorStake, 630 }, 631 &secp256k1fx.OutputOwners{ 632 Threshold: 1, 633 Addrs: []ids.ShortID{delRewardAddress}, 634 }, 635 ) 636 require.NoError(err) 637 delTx, err := walletsigner.SignUnsigned(context.Background(), signer, uDelTx) 638 require.NoError(err) 639 640 addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) 641 vdrRewardAmt := uint64(2000000) 642 vdrStaker, err := state.NewCurrentStaker( 643 vdrTx.ID(), 644 addValTx, 645 addValTx.StartTime(), 646 vdrRewardAmt, 647 ) 648 require.NoError(err) 649 650 addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) 651 delRewardAmt := uint64(1000000) 652 delStaker, err := state.NewCurrentStaker( 653 delTx.ID(), 654 addDelTx, 655 time.Unix(int64(delStartTime), 0), 656 delRewardAmt, 657 ) 658 require.NoError(err) 659 660 env.state.PutCurrentValidator(vdrStaker) 661 env.state.AddTx(vdrTx, status.Committed) 662 env.state.PutCurrentDelegator(delStaker) 663 env.state.AddTx(delTx, status.Committed) 664 env.state.SetTimestamp(time.Unix(int64(vdrEndTime), 0)) 665 env.state.SetHeight(dummyHeight) 666 require.NoError(env.state.Commit()) 667 668 vdrDestSet := set.Of(vdrRewardAddress) 669 delDestSet := set.Of(delRewardAddress) 670 671 oldVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 672 require.NoError(err) 673 oldDelBalance, err := avax.GetBalance(env.state, delDestSet) 674 require.NoError(err) 675 676 tx, err := newRewardValidatorTx(t, delTx.ID()) 677 require.NoError(err) 678 679 // Create Delegator Diffs 680 delOnCommitState, err := state.NewDiff(lastAcceptedID, env) 681 require.NoError(err) 682 683 delOnAbortState, err := state.NewDiff(lastAcceptedID, env) 684 require.NoError(err) 685 686 txExecutor := ProposalTxExecutor{ 687 OnCommitState: delOnCommitState, 688 OnAbortState: delOnAbortState, 689 Backend: &env.backend, 690 Tx: tx, 691 } 692 require.NoError(tx.Unsigned.Visit(&txExecutor)) 693 694 // Create Validator Diffs 695 testID := ids.GenerateTestID() 696 env.SetState(testID, delOnCommitState) 697 698 vdrOnCommitState, err := state.NewDiff(testID, env) 699 require.NoError(err) 700 701 vdrOnAbortState, err := state.NewDiff(testID, env) 702 require.NoError(err) 703 704 tx, err = newRewardValidatorTx(t, vdrTx.ID()) 705 require.NoError(err) 706 707 txExecutor = ProposalTxExecutor{ 708 OnCommitState: vdrOnCommitState, 709 OnAbortState: vdrOnAbortState, 710 Backend: &env.backend, 711 Tx: tx, 712 } 713 require.NoError(tx.Unsigned.Visit(&txExecutor)) 714 715 // aborted validator tx should still distribute accrued delegator rewards 716 numVdrStakeUTXOs := uint32(len(delTx.Unsigned.InputIDs())) 717 onAbortVdrDelRewardUTXOID := &avax.UTXOID{ 718 TxID: vdrTx.ID(), 719 OutputIndex: numVdrStakeUTXOs + 1, 720 } 721 722 utxo, err := vdrOnAbortState.GetUTXO(onAbortVdrDelRewardUTXOID.InputID()) 723 require.NoError(err) 724 require.IsType(&secp256k1fx.TransferOutput{}, utxo.Out) 725 castUTXO := utxo.Out.(*secp256k1fx.TransferOutput) 726 require.Equal(delRewardAmt/4, castUTXO.Amt, "expected validator to be rewarded with accrued delegator rewards") 727 require.True(vdrDestSet.Equals(castUTXO.AddressesSet()), "expected reward UTXO to be issued to vdrDestSet") 728 729 // Commit Delegator Diff 730 require.NoError(delOnCommitState.Apply(env.state)) 731 732 env.state.SetHeight(dummyHeight) 733 require.NoError(env.state.Commit()) 734 735 // Commit Validator Diff 736 require.NoError(vdrOnCommitState.Apply(env.state)) 737 738 env.state.SetHeight(dummyHeight) 739 require.NoError(env.state.Commit()) 740 741 // Since the tx was committed, the delegator and the delegatee should be rewarded. 742 // The delegator reward should be higher since the delegatee's share is 25%. 743 commitVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 744 require.NoError(err) 745 vdrReward, err := math.Sub(commitVdrBalance, oldVdrBalance) 746 require.NoError(err) 747 delegateeReward, err := math.Sub(vdrReward, vdrRewardAmt) 748 require.NoError(err) 749 require.NotZero(delegateeReward, "expected delegatee balance to increase because of reward") 750 751 commitDelBalance, err := avax.GetBalance(env.state, delDestSet) 752 require.NoError(err) 753 delReward, err := math.Sub(commitDelBalance, oldDelBalance) 754 require.NoError(err) 755 require.NotZero(delReward, "expected delegator balance to increase because of reward") 756 757 require.Less(delegateeReward, delReward, "the delegator's reward should be greater than the delegatee's because the delegatee's share is 25%") 758 require.Equal(delRewardAmt, delReward+delegateeReward, "expected total reward to be %d but is %d", delRewardAmt, delReward+vdrReward) 759 } 760 761 func TestRewardDelegatorTxExecuteOnAbort(t *testing.T) { 762 require := require.New(t) 763 env := newEnvironment(t, apricotPhase5) 764 dummyHeight := uint64(1) 765 766 initialSupply, err := env.state.GetCurrentSupply(constants.PrimaryNetworkID) 767 require.NoError(err) 768 769 vdrRewardAddress := ids.GenerateTestShortID() 770 delRewardAddress := ids.GenerateTestShortID() 771 772 vdrStartTime := uint64(defaultValidateStartTime.Unix()) + 1 773 vdrEndTime := uint64(defaultValidateStartTime.Add(2 * defaultMinStakingDuration).Unix()) 774 vdrNodeID := ids.GenerateTestNodeID() 775 776 builder, signer := env.factory.NewWallet(preFundedKeys[0]) 777 uVdrTx, err := builder.NewAddValidatorTx( 778 &txs.Validator{ 779 NodeID: vdrNodeID, 780 Start: vdrStartTime, 781 End: vdrEndTime, 782 Wght: env.config.MinValidatorStake, 783 }, 784 &secp256k1fx.OutputOwners{ 785 Threshold: 1, 786 Addrs: []ids.ShortID{vdrRewardAddress}, 787 }, 788 reward.PercentDenominator/4, 789 ) 790 require.NoError(err) 791 vdrTx, err := walletsigner.SignUnsigned(context.Background(), signer, uVdrTx) 792 require.NoError(err) 793 794 delStartTime := vdrStartTime 795 delEndTime := vdrEndTime 796 797 uDelTx, err := builder.NewAddDelegatorTx( 798 &txs.Validator{ 799 NodeID: vdrNodeID, 800 Start: delStartTime, 801 End: delEndTime, 802 Wght: env.config.MinDelegatorStake, 803 }, 804 &secp256k1fx.OutputOwners{ 805 Threshold: 1, 806 Addrs: []ids.ShortID{delRewardAddress}, 807 }, 808 ) 809 require.NoError(err) 810 delTx, err := walletsigner.SignUnsigned(context.Background(), signer, uDelTx) 811 require.NoError(err) 812 813 addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) 814 vdrStaker, err := state.NewCurrentStaker( 815 vdrTx.ID(), 816 addValTx, 817 addValTx.StartTime(), 818 0, 819 ) 820 require.NoError(err) 821 822 addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) 823 delStaker, err := state.NewCurrentStaker( 824 delTx.ID(), 825 addDelTx, 826 addDelTx.StartTime(), 827 1000000, 828 ) 829 require.NoError(err) 830 831 env.state.PutCurrentValidator(vdrStaker) 832 env.state.AddTx(vdrTx, status.Committed) 833 env.state.PutCurrentDelegator(delStaker) 834 env.state.AddTx(delTx, status.Committed) 835 env.state.SetTimestamp(time.Unix(int64(delEndTime), 0)) 836 env.state.SetHeight(dummyHeight) 837 require.NoError(env.state.Commit()) 838 839 tx, err := newRewardValidatorTx(t, delTx.ID()) 840 require.NoError(err) 841 842 onCommitState, err := state.NewDiff(lastAcceptedID, env) 843 require.NoError(err) 844 845 onAbortState, err := state.NewDiff(lastAcceptedID, env) 846 require.NoError(err) 847 848 txExecutor := ProposalTxExecutor{ 849 OnCommitState: onCommitState, 850 OnAbortState: onAbortState, 851 Backend: &env.backend, 852 Tx: tx, 853 } 854 require.NoError(tx.Unsigned.Visit(&txExecutor)) 855 856 vdrDestSet := set.Of(vdrRewardAddress) 857 delDestSet := set.Of(delRewardAddress) 858 859 expectedReward := uint64(1000000) 860 861 oldVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 862 require.NoError(err) 863 oldDelBalance, err := avax.GetBalance(env.state, delDestSet) 864 require.NoError(err) 865 866 require.NoError(txExecutor.OnAbortState.Apply(env.state)) 867 868 env.state.SetHeight(dummyHeight) 869 require.NoError(env.state.Commit()) 870 871 // If tx is aborted, delegator and delegatee shouldn't get reward 872 newVdrBalance, err := avax.GetBalance(env.state, vdrDestSet) 873 require.NoError(err) 874 vdrReward, err := math.Sub(newVdrBalance, oldVdrBalance) 875 require.NoError(err) 876 require.Zero(vdrReward, "expected delegatee balance not to increase") 877 878 newDelBalance, err := avax.GetBalance(env.state, delDestSet) 879 require.NoError(err) 880 delReward, err := math.Sub(newDelBalance, oldDelBalance) 881 require.NoError(err) 882 require.Zero(delReward, "expected delegator balance not to increase") 883 884 newSupply, err := env.state.GetCurrentSupply(constants.PrimaryNetworkID) 885 require.NoError(err) 886 require.Equal(initialSupply-expectedReward, newSupply, "should have removed un-rewarded tokens from the potential supply") 887 }