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