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  }