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 }