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  }