code.vegaprotocol.io/vega@v0.79.0/commands/transfer_funds.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package commands
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"math/big"
    22  
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  	"code.vegaprotocol.io/vega/protos/vega"
    25  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    26  )
    27  
    28  var (
    29  	ErrMustBeAfterStartEpoch = errors.New("must be after start_epoch")
    30  	ErrUnknownAsset          = errors.New("unknown asset")
    31  )
    32  
    33  func CheckTransfer(cmd *commandspb.Transfer) error {
    34  	return checkTransfer(cmd).ErrorOrNil()
    35  }
    36  
    37  func checkTransfer(cmd *commandspb.Transfer) (e Errors) {
    38  	errs := NewErrors()
    39  
    40  	if cmd == nil {
    41  		return errs.FinalAddForProperty("transfer", ErrIsRequired)
    42  	}
    43  
    44  	if len(cmd.Amount) <= 0 {
    45  		errs.AddForProperty("transfer.amount", ErrIsRequired)
    46  	} else {
    47  		if amount, ok := big.NewInt(0).SetString(cmd.Amount, 10); !ok {
    48  			errs.AddForProperty("transfer.amount", ErrNotAValidInteger)
    49  		} else {
    50  			if amount.Cmp(big.NewInt(0)) == 0 {
    51  				errs.AddForProperty("transfer.amount", ErrIsRequired)
    52  			}
    53  			if amount.Cmp(big.NewInt(0)) == -1 {
    54  				errs.AddForProperty("transfer.amount", ErrMustBePositive)
    55  			}
    56  		}
    57  	}
    58  
    59  	if len(cmd.To) <= 0 {
    60  		errs.AddForProperty("transfer.to", ErrIsRequired)
    61  	} else if !IsVegaPublicKey(cmd.To) {
    62  		errs.AddForProperty("transfer.to", ErrShouldBeAValidVegaPublicKey)
    63  	}
    64  
    65  	if cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_UNSPECIFIED {
    66  		errs.AddForProperty("transfer.to_account_type", ErrIsNotValid)
    67  	} else if _, ok := vega.AccountType_name[int32(cmd.ToAccountType)]; !ok {
    68  		errs.AddForProperty("transfer.to_account_type", ErrIsNotValid)
    69  	}
    70  
    71  	// if the transfer is to a reward account, it must have the to set to 0
    72  	if cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_GLOBAL_REWARD && cmd.To != "0000000000000000000000000000000000000000000000000000000000000000" {
    73  		errs.AddForProperty("transfer.to_account_type", ErrIsNotValid)
    74  	}
    75  
    76  	if cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL &&
    77  		cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_VESTED_REWARDS &&
    78  		cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_LOCKED_FOR_STAKING {
    79  		errs.AddForProperty("transfer.from_account_type", ErrIsNotValid)
    80  	}
    81  
    82  	if len(cmd.Asset) <= 0 {
    83  		errs.AddForProperty("transfer.asset", ErrIsRequired)
    84  	} else if !IsVegaID(cmd.Asset) {
    85  		errs.AddForProperty("transfer.asset", ErrShouldBeAValidVegaID)
    86  	}
    87  
    88  	// arbitrary 100 char length for now
    89  	if len(cmd.Reference) > 100 {
    90  		errs.AddForProperty("transfer.reference", ErrMustBeLessThan100Chars)
    91  	}
    92  
    93  	// derived key check
    94  	if cmd.From != nil {
    95  		if !IsVegaPublicKey(*cmd.From) {
    96  			errs.AddForProperty("transfer.from", ErrShouldBeAValidVegaPublicKey)
    97  		}
    98  
    99  		if cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_VESTED_REWARDS {
   100  			errs.AddForProperty("transfer.from", errors.New("from can only be set for vested rewards"))
   101  		}
   102  
   103  		if cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL {
   104  			errs.AddForProperty("transfer.from", errors.New("from can only be set when transferring to general account"))
   105  		}
   106  	}
   107  
   108  	if cmd.Kind == nil {
   109  		errs.AddForProperty("transfer.kind", ErrIsRequired)
   110  	} else {
   111  		switch k := cmd.Kind.(type) {
   112  		case *commandspb.Transfer_OneOff:
   113  			if cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GLOBAL_REWARD && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_UNSPECIFIED && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_NETWORK_TREASURY && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_BUY_BACK_FEES && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_LOCKED_FOR_STAKING {
   114  				errs.AddForProperty("transfer.to_account_type", errors.New("account type is not valid for one off transfer"))
   115  			}
   116  			if k.OneOff.GetDeliverOn() < 0 {
   117  				errs.AddForProperty("transfer.kind.deliver_on", ErrMustBePositiveOrZero)
   118  			}
   119  			// do not allow for one off transfer to one of the metric based accounts
   120  			if cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES ||
   121  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES ||
   122  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES ||
   123  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS ||
   124  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL ||
   125  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RELATIVE_RETURN ||
   126  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY ||
   127  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_REALISED_RETURN ||
   128  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING ||
   129  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES {
   130  				errs.AddForProperty("transfer.account.to", errors.New("transfers to metric-based reward accounts must be recurring transfers that specify a distribution metric"))
   131  			}
   132  		case *commandspb.Transfer_Recurring:
   133  			if cmd.FromAccountType == vega.AccountType_ACCOUNT_TYPE_VESTED_REWARDS {
   134  				errs.AddForProperty("transfer.from_account_type", errors.New("account type is not valid for one recurring transfer"))
   135  			}
   136  			if k.Recurring.EndEpoch != nil && *k.Recurring.EndEpoch <= 0 {
   137  				errs.AddForProperty("transfer.kind.end_epoch", ErrMustBePositive)
   138  			}
   139  			if k.Recurring.StartEpoch == 0 {
   140  				errs.AddForProperty("transfer.kind.start_epoch", ErrMustBePositive)
   141  			}
   142  			if k.Recurring.EndEpoch != nil && k.Recurring.StartEpoch > *k.Recurring.EndEpoch {
   143  				errs.AddForProperty("transfer.kind.end_epoch", ErrMustBeAfterStartEpoch)
   144  			}
   145  			if f, ok := big.NewFloat(0).SetString(k.Recurring.Factor); !ok {
   146  				errs.AddForProperty("transfer.kind.factor", ErrNotAValidFloat)
   147  			} else {
   148  				if f.Cmp(big.NewFloat(0)) <= 0 {
   149  					errs.AddForProperty("transfer.kind.factor", ErrMustBePositive)
   150  				}
   151  			}
   152  			if cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES ||
   153  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES ||
   154  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES ||
   155  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS ||
   156  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL ||
   157  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RELATIVE_RETURN ||
   158  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_REALISED_RETURN ||
   159  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY ||
   160  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES ||
   161  				cmd.ToAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING {
   162  				if k.Recurring.DispatchStrategy == nil {
   163  					errs.AddForProperty("transfer.kind.dispatch_strategy", ErrIsRequired)
   164  				}
   165  			}
   166  			// dispatch strategy only makes sense for reward pools
   167  			if k.Recurring.DispatchStrategy != nil {
   168  				validateDispatchStrategy(cmd.ToAccountType, k.Recurring.DispatchStrategy, errs, "transfer.kind.dispatch_strategy", "transfer.account.to")
   169  			}
   170  
   171  		default:
   172  			errs.AddForProperty("transfer.kind", ErrIsNotSupported)
   173  		}
   174  	}
   175  
   176  	return errs
   177  }
   178  
   179  func mismatchingAccountTypeError(tp vega.AccountType, metric vega.DispatchMetric) error {
   180  	return errors.New("cannot set toAccountType to " + tp.String() + " when dispatch metric is set to " + metric.String())
   181  }
   182  
   183  func validateDispatchStrategy(toAccountType vega.AccountType, dispatchStrategy *vega.DispatchStrategy, errs Errors, prefix string, destinationPrefixErr string) {
   184  	// check account type is one of the relevant reward accounts
   185  	if toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES &&
   186  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES &&
   187  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES &&
   188  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS &&
   189  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL &&
   190  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_RELATIVE_RETURN &&
   191  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_REALISED_RETURN &&
   192  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY &&
   193  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES &&
   194  		toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING {
   195  		errs.AddForProperty(destinationPrefixErr, ErrIsNotValid)
   196  	}
   197  	// check asset for metric is passed unless it's a market proposer reward
   198  	if len(dispatchStrategy.AssetForMetric) <= 0 && toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES && toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS && toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING {
   199  		errs.AddForProperty(prefix+".asset_for_metric", ErrIsRequired)
   200  	}
   201  	if len(dispatchStrategy.AssetForMetric) > 0 && !IsVegaID(dispatchStrategy.AssetForMetric) {
   202  		errs.AddForProperty(prefix+".asset_for_metric", ErrShouldBeAValidVegaID)
   203  	}
   204  	if len(dispatchStrategy.AssetForMetric) > 0 && toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING {
   205  		errs.AddForProperty(prefix+".asset_for_metric", errors.New("not be specified when to_account type is VALIDATOR_RANKING"))
   206  	}
   207  	// check that that the metric makes sense for the account type
   208  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED {
   209  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   210  	}
   211  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED {
   212  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   213  	}
   214  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID {
   215  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   216  	}
   217  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_MARKET_VALUE {
   218  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   219  	}
   220  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL {
   221  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   222  	}
   223  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES {
   224  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   225  	}
   226  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RELATIVE_RETURN && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN {
   227  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   228  	}
   229  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_REALISED_RETURN && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN {
   230  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   231  	}
   232  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY {
   233  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   234  	}
   235  	if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING && dispatchStrategy.Metric != vega.DispatchMetric_DISPATCH_METRIC_VALIDATOR_RANKING {
   236  		errs.AddForProperty(prefix+".dispatch_metric", mismatchingAccountTypeError(toAccountType, dispatchStrategy.Metric))
   237  	}
   238  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_UNSPECIFIED {
   239  		errs.AddForProperty(prefix+".entity_scope", ErrIsRequired)
   240  	}
   241  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_TEAMS && toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   242  		errs.AddForProperty(prefix+".entity_scope", errors.New(vega.EntityScope_ENTITY_SCOPE_TEAMS.String()+" is not allowed for "+toAccountType.String()))
   243  	}
   244  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_TEAMS && len(dispatchStrategy.NTopPerformers) == 0 {
   245  		errs.AddForProperty(prefix+".n_top_performers", ErrIsRequired)
   246  	}
   247  	if dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES {
   248  		var metricAssetDefined, marketScope, stakingRequirement, positionRequirement bool
   249  		if len(dispatchStrategy.AssetForMetric) > 0 {
   250  			metricAssetDefined = true
   251  		}
   252  		if len(dispatchStrategy.Markets) > 0 {
   253  			marketScope = true
   254  		}
   255  		if len(dispatchStrategy.StakingRequirement) > 0 {
   256  			stakingRequirement = true
   257  		}
   258  		if len(dispatchStrategy.NotionalTimeWeightedAveragePositionRequirement) > 0 {
   259  			positionRequirement = true
   260  		}
   261  		if !metricAssetDefined && !marketScope && !stakingRequirement && !positionRequirement {
   262  			errs.AddForProperty(prefix+".dispatch_metric", fmt.Errorf("eligible_entities metric requires at least one of (markets, asset_for_metric, staking_requirement, notional_time_weighted_average_position_requirement) to be defined"))
   263  		}
   264  	}
   265  
   266  	if dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES && len(dispatchStrategy.NotionalTimeWeightedAveragePositionRequirement) > 0 && len(dispatchStrategy.AssetForMetric) == 0 {
   267  		errs.AddForProperty(prefix+".asset_for_metric", fmt.Errorf("asset for metric must be provided if NotionalTimeWeightedAveragePositionRequirement is specified"))
   268  	}
   269  
   270  	if dispatchStrategy.EntityScope != vega.EntityScope_ENTITY_SCOPE_TEAMS && len(dispatchStrategy.NTopPerformers) != 0 {
   271  		errs.AddForProperty(prefix+".n_top_performers", errors.New("must not be set when entity scope is not "+vega.EntityScope_ENTITY_SCOPE_TEAMS.String()))
   272  	}
   273  
   274  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_TEAMS && len(dispatchStrategy.NTopPerformers) > 0 {
   275  		nTopPerformers, err := num.DecimalFromString(dispatchStrategy.NTopPerformers)
   276  		if err != nil {
   277  			errs.AddForProperty(prefix+".n_top_performers", ErrIsNotValidNumber)
   278  		} else if nTopPerformers.LessThanOrEqual(num.DecimalZero()) {
   279  			errs.AddForProperty(prefix+".n_top_performers", ErrMustBeBetween01)
   280  		} else if nTopPerformers.GreaterThan(num.DecimalOne()) {
   281  			errs.AddForProperty(prefix+".n_top_performers", ErrMustBeBetween01)
   282  		}
   283  	}
   284  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS && dispatchStrategy.IndividualScope == vega.IndividualScope_INDIVIDUAL_SCOPE_UNSPECIFIED {
   285  		errs.AddForProperty(prefix+".individual_scope", ErrIsRequired)
   286  	}
   287  
   288  	if dispatchStrategy.EntityScope == vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS && len(dispatchStrategy.TeamScope) > 0 {
   289  		errs.AddForProperty(prefix+".team_scope", errors.New("should not be set when entity_scope is set to "+dispatchStrategy.EntityScope.String()))
   290  	}
   291  
   292  	if dispatchStrategy.EntityScope != vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS && dispatchStrategy.IndividualScope != vega.IndividualScope_INDIVIDUAL_SCOPE_UNSPECIFIED {
   293  		errs.AddForProperty(prefix+".individual_scope", errors.New("should not be set when entity_scope is set to "+dispatchStrategy.EntityScope.String()))
   294  	}
   295  	if dispatchStrategy.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_UNSPECIFIED && toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   296  		errs.AddForProperty(prefix+".distribution_strategy", ErrIsRequired)
   297  	}
   298  	if dispatchStrategy.DistributionStrategy != vega.DistributionStrategy_DISTRIBUTION_STRATEGY_UNSPECIFIED && toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   299  		errs.AddForProperty(prefix+".distribution_strategy", errors.New("should not be set when to_account is set to "+toAccountType.String()))
   300  	}
   301  	if len(dispatchStrategy.StakingRequirement) > 0 {
   302  		if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING || toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   303  			errs.AddForProperty(prefix+".staking_requirement", errors.New("should not be set if to_account is set to "+toAccountType.String()))
   304  		} else if staking, ok := big.NewInt(0).SetString(dispatchStrategy.StakingRequirement, 10); !ok {
   305  			errs.AddForProperty(prefix+".staking_requirement", ErrNotAValidInteger)
   306  		} else if staking.Cmp(big.NewInt(0)) < 0 {
   307  			errs.AddForProperty(prefix+".staking_requirement", ErrMustBePositiveOrZero)
   308  		}
   309  	}
   310  	if len(dispatchStrategy.NotionalTimeWeightedAveragePositionRequirement) > 0 {
   311  		if toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING || toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   312  			errs.AddForProperty(prefix+".notional_time_weighted_average_position_requirement", errors.New("should not be set if to_account is set to "+toAccountType.String()))
   313  		} else if notional, ok := big.NewInt(0).SetString(dispatchStrategy.NotionalTimeWeightedAveragePositionRequirement, 10); !ok {
   314  			errs.AddForProperty(prefix+".notional_time_weighted_average_position_requirement", ErrNotAValidInteger)
   315  		} else if notional.Cmp(big.NewInt(0)) < 0 {
   316  			errs.AddForProperty(prefix+".notional_time_weighted_average_position_requirement", ErrMustBePositiveOrZero)
   317  		}
   318  	}
   319  	if dispatchStrategy.WindowLength > 0 && toAccountType == vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   320  		errs.AddForProperty(prefix+".window_length", errors.New("should not be set for "+vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS.String()))
   321  	}
   322  	if dispatchStrategy.WindowLength == 0 && toAccountType != vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS {
   323  		errs.AddForProperty(prefix+".window_length", errors.New("must be between 1 and 100"))
   324  	}
   325  	if dispatchStrategy.WindowLength > 100 {
   326  		errs.AddForProperty(prefix+".window_length", ErrMustBeAtMost100)
   327  	}
   328  	if len(dispatchStrategy.RankTable) == 0 && (dispatchStrategy.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK || dispatchStrategy.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK_LOTTERY) {
   329  		errs.AddForProperty(prefix+".rank_table", ErrMustBePositive)
   330  	}
   331  	if len(dispatchStrategy.RankTable) > 0 && dispatchStrategy.DistributionStrategy != vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK && dispatchStrategy.DistributionStrategy != vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK_LOTTERY {
   332  		errs.AddForProperty(prefix+".rank_table", errors.New("should not be set for distribution strategy "+dispatchStrategy.DistributionStrategy.String()))
   333  	}
   334  	if len(dispatchStrategy.RankTable) > 500 {
   335  		errs.AddForProperty(prefix+".rank_table", ErrMustBeAtMost500)
   336  	}
   337  	if len(dispatchStrategy.RankTable) > 1 {
   338  		for i := 1; i < len(dispatchStrategy.RankTable); i++ {
   339  			if dispatchStrategy.RankTable[i].StartRank <= dispatchStrategy.RankTable[i-1].StartRank {
   340  				errs.AddForProperty(fmt.Sprintf(prefix+".rank_table.%i.start_rank", i), fmt.Errorf("must be greater than start_rank of element #%d", i-1))
   341  				break
   342  			}
   343  		}
   344  	}
   345  	if dispatchStrategy.CapRewardFeeMultiple != nil && len(*dispatchStrategy.CapRewardFeeMultiple) > 0 {
   346  		cap, err := num.DecimalFromString(*dispatchStrategy.CapRewardFeeMultiple)
   347  		if err != nil {
   348  			errs.AddForProperty(prefix+".cap_reward_fee_multiple", ErrIsNotValidNumber)
   349  		} else {
   350  			if cap.LessThanOrEqual(num.DecimalZero()) {
   351  				errs.AddForProperty(prefix+".cap_reward_fee_multiple", ErrMustBePositive)
   352  			}
   353  		}
   354  	}
   355  
   356  	if dispatchStrategy.TransferInterval != nil && (*dispatchStrategy.TransferInterval <= 0 || *dispatchStrategy.TransferInterval > 100) {
   357  		errs.AddForProperty(prefix+".transfer_interval", errors.New("must be between 1 and 100"))
   358  	}
   359  
   360  	if dispatchStrategy.TargetNotionalVolume != nil &&
   361  		((dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES && len(dispatchStrategy.AssetForMetric) == 0) ||
   362  			dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_MARKET_VALUE ||
   363  			dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_VALIDATOR_RANKING) {
   364  		errs.AddForProperty(prefix+".target_notional_volume", fmt.Errorf(fmt.Sprintf("not allowed for metric %s", dispatchStrategy.Metric)))
   365  	}
   366  	if dispatchStrategy.TargetNotionalVolume != nil && len(*dispatchStrategy.TargetNotionalVolume) > 0 {
   367  		n, overflow := num.UintFromString(*dispatchStrategy.TargetNotionalVolume, 10)
   368  		if overflow {
   369  			errs.AddForProperty(prefix+".target_notional_volume", ErrIsNotValidNumber)
   370  		} else if n.IsNegative() || n.IsZero() {
   371  			errs.AddForProperty(prefix+".target_notional_volume", ErrMustBePositive)
   372  		}
   373  	}
   374  }