code.vegaprotocol.io/vega@v0.79.0/core/governance/market.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 governance 17 18 import ( 19 "errors" 20 "fmt" 21 "strconv" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/datasource" 25 dsdefinition "code.vegaprotocol.io/vega/core/datasource/definition" 26 ethcallcommon "code.vegaprotocol.io/vega/core/datasource/external/ethcall/common" 27 "code.vegaprotocol.io/vega/core/datasource/spec" 28 "code.vegaprotocol.io/vega/core/execution/liquidation" 29 "code.vegaprotocol.io/vega/core/netparams" 30 "code.vegaprotocol.io/vega/core/types" 31 "code.vegaprotocol.io/vega/libs/num" 32 "code.vegaprotocol.io/vega/libs/ptr" 33 proto "code.vegaprotocol.io/vega/protos/vega" 34 datapb "code.vegaprotocol.io/vega/protos/vega/data/v1" 35 ) 36 37 var ( 38 // ErrMissingProduct is returned if selected product is nil. 39 ErrMissingProduct = errors.New("missing product") 40 // ErrUnsupportedProduct is returned if selected product is not supported. 41 ErrUnsupportedProduct = errors.New("product type is not supported") 42 // ErrUnsupportedRiskParameters is returned if risk parameters supplied via governance are not yet supported. 43 ErrUnsupportedRiskParameters = errors.New("risk model parameters are not supported") 44 // ErrMissingRiskParameters ... 45 ErrMissingRiskParameters = errors.New("missing risk parameters") 46 // ErrMissingDataSourceSpecBinding is returned when the data source spec binding is absent. 47 ErrMissingDataSourceSpecBinding = errors.New("missing data source spec binding") 48 // ErrMissingDataSourceSpecForSettlementData is returned when the data source spec for settlement data is absent. 49 ErrMissingDataSourceSpecForSettlementData = errors.New("missing data source spec for settlement data") 50 // ErrMissingDataSourceSpecForSettlementData is returned when the data source spec for settlement data is absent. 51 ErrSettlementWithInternalDataSourceIsNotAllowed = errors.New("settlement with internal data source is not allwed") 52 // ErrMissingDataSourceSpecForTradingTermination is returned when the data source spec for trading termination is absent. 53 ErrMissingDataSourceSpecForTradingTermination = errors.New("missing data source spec for trading termination") 54 // ErrMissingDataSourceSpecForSettlementSchedule is returned when the data source spec for trading termination is absent. 55 ErrMissingDataSourceSpecForSettlementSchedule = errors.New("missing data source spec for settlement schedule") 56 // ErrInternalTimeTriggerForFuturesInNotAllowed is returned when a proposal containing timetrigger terminaiton type of data is received. 57 ErrInternalTimeTriggerForFuturesInNotAllowed = errors.New("setting internal time trigger for future termination is not allowed") 58 // ErrDataSourceSpecTerminationTimeBeforeEnactment is returned when termination time is before enactment 59 // for time triggered termination condition. 60 ErrDataSourceSpecTerminationTimeBeforeEnactment = errors.New("data source spec termination time before enactment") 61 // ErrMissingPerpsProduct is returned when perps product is absent from the instrument. 62 ErrMissingPerpsProduct = errors.New("missing perps product") 63 // ErrMissingFutureProduct is returned when future product is absent from the instrument. 64 ErrMissingFutureProduct = errors.New("missing future product") 65 // ErrMissingSpotProduct is returned when spot product is absent from the instrument. 66 ErrMissingSpotProduct = errors.New("missing spot product") 67 // ErrInvalidRiskParameter ... 68 ErrInvalidRiskParameter = errors.New("invalid risk parameter") 69 // ErrInvalidInsurancePoolFraction is returned if the insurance pool fraction parameter is outside of the 0-1 range. 70 ErrInvalidInsurancePoolFraction = errors.New("insurnace pool fraction invalid") 71 ErrUpdateMarketDifferentProduct = errors.New("cannot update a market to a different product type") 72 ErrInvalidEVMChainIDInEthereumOracleSpec = errors.New("invalid source chain id in ethereum oracle spec") 73 ErrMaxPriceInvalid = errors.New("max price for capped future must be greater than zero") 74 ) 75 76 const defaultAllowedEmptyAMMLevels = uint64(100) 77 78 func assignProduct( 79 source *types.InstrumentConfiguration, 80 target *types.Instrument, 81 ) (proto.ProposalError, error) { 82 switch product := source.Product.(type) { 83 case *types.InstrumentConfigurationFuture: 84 if product.Future == nil { 85 return types.ProposalErrorInvalidFutureProduct, ErrMissingFutureProduct 86 } 87 settlData := &product.Future.DataSourceSpecForSettlementData 88 if settlData == nil { 89 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForSettlementData 90 } 91 92 tterm := &product.Future.DataSourceSpecForTradingTermination 93 if tterm == nil { 94 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForTradingTermination 95 } 96 if product.Future.DataSourceSpecBinding == nil { 97 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecBinding 98 } 99 100 target.Product = &types.InstrumentFuture{ 101 Future: &types.Future{ 102 SettlementAsset: product.Future.SettlementAsset, 103 QuoteName: product.Future.QuoteName, 104 DataSourceSpecForSettlementData: datasource.SpecFromDefinition(product.Future.DataSourceSpecForSettlementData), 105 DataSourceSpecForTradingTermination: datasource.SpecFromDefinition(product.Future.DataSourceSpecForTradingTermination), 106 DataSourceSpecBinding: product.Future.DataSourceSpecBinding, 107 Cap: product.Future.Cap, 108 }, 109 } 110 case *types.InstrumentConfigurationPerps: 111 if product.Perps == nil { 112 return types.ProposalErrorInvalidPerpsProduct, ErrMissingPerpsProduct 113 } 114 settlData := &product.Perps.DataSourceSpecForSettlementData 115 if settlData == nil { 116 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementData 117 } 118 119 settlSchedule := &product.Perps.DataSourceSpecForSettlementSchedule 120 if settlSchedule == nil { 121 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForTradingTermination 122 } 123 if product.Perps.DataSourceSpecBinding == nil { 124 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecBinding 125 } 126 127 target.Product = &types.InstrumentPerps{ 128 Perps: &types.Perps{ 129 SettlementAsset: product.Perps.SettlementAsset, 130 QuoteName: product.Perps.QuoteName, 131 InterestRate: product.Perps.InterestRate, 132 MarginFundingFactor: product.Perps.MarginFundingFactor, 133 ClampLowerBound: product.Perps.ClampLowerBound, 134 ClampUpperBound: product.Perps.ClampUpperBound, 135 FundingRateScalingFactor: product.Perps.FundingRateScalingFactor, 136 FundingRateLowerBound: product.Perps.FundingRateLowerBound, 137 FundingRateUpperBound: product.Perps.FundingRateUpperBound, 138 DataSourceSpecForSettlementData: datasource.SpecFromDefinition(product.Perps.DataSourceSpecForSettlementData), 139 DataSourceSpecForSettlementSchedule: datasource.SpecFromDefinition(product.Perps.DataSourceSpecForSettlementSchedule), 140 DataSourceSpecBinding: product.Perps.DataSourceSpecBinding, 141 InternalCompositePriceConfig: product.Perps.InternalCompositePriceConfig, 142 }, 143 } 144 case *types.InstrumentConfigurationSpot: 145 if product.Spot == nil { 146 return types.ProposalErrorInvalidSpot, ErrMissingSpotProduct 147 } 148 149 target.Product = &types.InstrumentSpot{ 150 Spot: &types.Spot{ 151 Name: product.Spot.Name, 152 BaseAsset: product.Spot.BaseAsset, 153 QuoteAsset: product.Spot.QuoteAsset, 154 }, 155 } 156 157 default: 158 return types.ProposalErrorUnsupportedProduct, ErrUnsupportedProduct 159 } 160 return types.ProposalErrorUnspecified, nil 161 } 162 163 func createInstrument( 164 input *types.InstrumentConfiguration, 165 tags []string, 166 ) (*types.Instrument, types.ProposalError, error) { 167 result := &types.Instrument{ 168 Name: input.Name, 169 Code: input.Code, 170 Metadata: &types.InstrumentMetadata{ 171 Tags: tags, 172 }, 173 } 174 175 if perr, err := assignProduct(input, result); err != nil { 176 return nil, perr, err 177 } 178 return result, types.ProposalErrorUnspecified, nil 179 } 180 181 func assignRiskModel(definition *types.NewMarketConfiguration, target *types.TradableInstrument) error { 182 switch parameters := definition.RiskParameters.(type) { 183 case *types.NewMarketConfigurationSimple: 184 target.RiskModel = &types.TradableInstrumentSimpleRiskModel{ 185 SimpleRiskModel: &types.SimpleRiskModel{ 186 Params: parameters.Simple, 187 }, 188 } 189 case *types.NewMarketConfigurationLogNormal: 190 target.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 191 LogNormalRiskModel: parameters.LogNormal, 192 } 193 default: 194 return ErrUnsupportedRiskParameters 195 } 196 return nil 197 } 198 199 func assignSpotRiskModel(definition *types.NewSpotMarketConfiguration, target *types.TradableInstrument) error { 200 switch parameters := definition.RiskParameters.(type) { 201 case *types.NewSpotMarketConfigurationSimple: 202 target.RiskModel = &types.TradableInstrumentSimpleRiskModel{ 203 SimpleRiskModel: &types.SimpleRiskModel{ 204 Params: parameters.Simple, 205 }, 206 } 207 case *types.NewSpotMarketConfigurationLogNormal: 208 target.RiskModel = &types.TradableInstrumentLogNormalRiskModel{ 209 LogNormalRiskModel: parameters.LogNormal, 210 } 211 default: 212 return ErrUnsupportedRiskParameters 213 } 214 return nil 215 } 216 217 func buildMarketFromProposal( 218 marketID string, 219 definition *types.NewMarket, 220 netp NetParams, 221 openingAuctionDuration time.Duration, 222 ) (*types.Market, types.ProposalError, error) { 223 instrument, perr, err := createInstrument(definition.Changes.Instrument, definition.Changes.Metadata) 224 if err != nil { 225 return nil, perr, err 226 } 227 228 // get factors for the market 229 makerFee, _ := netp.Get(netparams.MarketFeeFactorsMakerFee) 230 infraFee, _ := netp.Get(netparams.MarketFeeFactorsInfrastructureFee) 231 buybackFee, _ := netp.Get(netparams.MarketFeeFactorsBuyBackFee) 232 treasuryFee, _ := netp.Get(netparams.MarketFeeFactorsTreasuryFee) 233 234 // get the margin scaling factors 235 scalingFactors := proto.ScalingFactors{} 236 _ = netp.GetJSONStruct(netparams.MarketMarginScalingFactors, &scalingFactors) 237 // get price monitoring parameters 238 if definition.Changes.PriceMonitoringParameters == nil { 239 pmParams := &proto.PriceMonitoringParameters{} 240 _ = netp.GetJSONStruct(netparams.MarketPriceMonitoringDefaultParameters, pmParams) 241 definition.Changes.PriceMonitoringParameters = types.PriceMonitoringParametersFromProto(pmParams) 242 } 243 244 // if a liquidity fee setting isn't supplied in the proposal, we'll default to margin-cost. 245 if definition.Changes.LiquidityFeeSettings == nil { 246 definition.Changes.LiquidityFeeSettings = &types.LiquidityFeeSettings{ 247 Method: proto.LiquidityFeeSettings_METHOD_MARGINAL_COST, 248 } 249 } 250 251 // this can be nil for market updates. 252 var lstrat *types.LiquidationStrategy 253 if definition.Changes.LiquidationStrategy != nil { 254 lstrat = definition.Changes.LiquidationStrategy.DeepClone() 255 } 256 257 allowedEmptyAMMLevels := defaultAllowedEmptyAMMLevels 258 if definition.Changes.AllowedEmptyAmmLevels != nil { 259 allowedEmptyAMMLevels = *definition.Changes.AllowedEmptyAmmLevels 260 } 261 262 makerFeeDec, _ := num.DecimalFromString(makerFee) 263 infraFeeDec, _ := num.DecimalFromString(infraFee) 264 buybackFeeDec, _ := num.DecimalFromString(buybackFee) 265 treasuryFeeDec, _ := num.DecimalFromString(treasuryFee) 266 // assign here, we want to update this after assigning market variable 267 marginCalc := &types.MarginCalculator{ 268 ScalingFactors: types.ScalingFactorsFromProto(&scalingFactors), 269 } 270 market := &types.Market{ 271 ID: marketID, 272 DecimalPlaces: definition.Changes.DecimalPlaces, 273 PositionDecimalPlaces: definition.Changes.PositionDecimalPlaces, 274 Fees: &types.Fees{ 275 Factors: &types.FeeFactors{ 276 MakerFee: makerFeeDec, 277 InfrastructureFee: infraFeeDec, 278 TreasuryFee: treasuryFeeDec, 279 BuyBackFee: buybackFeeDec, 280 }, 281 LiquidityFeeSettings: definition.Changes.LiquidityFeeSettings, 282 }, 283 OpeningAuction: &types.AuctionDuration{ 284 Duration: int64(openingAuctionDuration.Seconds()), 285 }, 286 TradableInstrument: &types.TradableInstrument{ 287 Instrument: instrument, 288 MarginCalculator: marginCalc, 289 }, 290 PriceMonitoringSettings: &types.PriceMonitoringSettings{ 291 Parameters: definition.Changes.PriceMonitoringParameters, 292 }, 293 LiquidityMonitoringParameters: definition.Changes.LiquidityMonitoringParameters, 294 LiquiditySLAParams: definition.Changes.LiquiditySLAParameters, 295 LinearSlippageFactor: definition.Changes.LinearSlippageFactor, 296 QuadraticSlippageFactor: definition.Changes.QuadraticSlippageFactor, 297 LiquidationStrategy: lstrat, 298 MarkPriceConfiguration: definition.Changes.MarkPriceConfiguration, 299 TickSize: definition.Changes.TickSize, 300 EnableTxReordering: definition.Changes.EnableTxReordering, 301 AllowedEmptyAmmLevels: allowedEmptyAMMLevels, 302 } 303 if fCap := market.TradableInstrument.Instrument.Product.Cap(); fCap != nil { 304 marginCalc.FullyCollateralised = fCap.FullyCollateralised 305 } 306 // successor proposal 307 if suc := definition.Successor(); suc != nil { 308 market.ParentMarketID = suc.ParentID 309 market.InsurancePoolFraction = suc.InsurancePoolFraction 310 } 311 if err := assignRiskModel(definition.Changes, market.TradableInstrument); err != nil { 312 return nil, types.ProposalErrorUnspecified, err 313 } 314 return market, types.ProposalErrorUnspecified, nil 315 } 316 317 func buildSpotMarketFromProposal( 318 marketID string, 319 definition *types.NewSpotMarket, 320 netp NetParams, 321 openingAuctionDuration time.Duration, 322 ) (*types.Market, types.ProposalError, error) { 323 instrument, perr, err := createInstrument(definition.Changes.Instrument, definition.Changes.Metadata) 324 if err != nil { 325 return nil, perr, err 326 } 327 328 // get factors for the market 329 makerFee, _ := netp.Get(netparams.MarketFeeFactorsMakerFee) 330 infraFee, _ := netp.Get(netparams.MarketFeeFactorsInfrastructureFee) 331 buybackFee, _ := netp.Get(netparams.MarketFeeFactorsBuyBackFee) 332 treasuryFee, _ := netp.Get(netparams.MarketFeeFactorsTreasuryFee) 333 // get price monitoring parameters 334 if definition.Changes.PriceMonitoringParameters == nil { 335 pmParams := &proto.PriceMonitoringParameters{} 336 _ = netp.GetJSONStruct(netparams.MarketPriceMonitoringDefaultParameters, pmParams) 337 definition.Changes.PriceMonitoringParameters = types.PriceMonitoringParametersFromProto(pmParams) 338 } 339 340 // if a liquidity fee setting isn't supplied in the proposal, we'll default to margin-cost. 341 if definition.Changes.LiquidityFeeSettings == nil { 342 definition.Changes.LiquidityFeeSettings = &types.LiquidityFeeSettings{ 343 Method: proto.LiquidityFeeSettings_METHOD_MARGINAL_COST, 344 } 345 } 346 347 liquidityMonitoring := &types.LiquidityMonitoringParameters{ 348 TargetStakeParameters: definition.Changes.TargetStakeParameters, 349 } 350 351 makerFeeDec, _ := num.DecimalFromString(makerFee) 352 infraFeeDec, _ := num.DecimalFromString(infraFee) 353 buybackFeeDec, _ := num.DecimalFromString(buybackFee) 354 treasuryFeeDec, _ := num.DecimalFromString(treasuryFee) 355 market := &types.Market{ 356 ID: marketID, 357 DecimalPlaces: definition.Changes.PriceDecimalPlaces, 358 PositionDecimalPlaces: definition.Changes.SizeDecimalPlaces, 359 Fees: &types.Fees{ 360 Factors: &types.FeeFactors{ 361 MakerFee: makerFeeDec, 362 InfrastructureFee: infraFeeDec, 363 TreasuryFee: treasuryFeeDec, 364 BuyBackFee: buybackFeeDec, 365 }, 366 LiquidityFeeSettings: definition.Changes.LiquidityFeeSettings, 367 }, 368 OpeningAuction: &types.AuctionDuration{ 369 Duration: int64(openingAuctionDuration.Seconds()), 370 }, 371 TradableInstrument: &types.TradableInstrument{ 372 Instrument: instrument, 373 MarginCalculator: &types.MarginCalculator{ 374 ScalingFactors: &types.ScalingFactors{ 375 SearchLevel: num.DecimalZero(), 376 InitialMargin: num.DecimalZero(), 377 CollateralRelease: num.DecimalZero(), 378 }, 379 }, 380 }, 381 PriceMonitoringSettings: &types.PriceMonitoringSettings{ 382 Parameters: definition.Changes.PriceMonitoringParameters, 383 }, 384 LiquidityMonitoringParameters: liquidityMonitoring, 385 LinearSlippageFactor: num.DecimalZero(), 386 QuadraticSlippageFactor: num.DecimalZero(), 387 LiquiditySLAParams: definition.Changes.SLAParams, 388 MarkPriceConfiguration: defaultMarkPriceConfig, 389 TickSize: definition.Changes.TickSize, 390 EnableTxReordering: definition.Changes.EnableTxReordering, 391 AllowedSellers: append([]string{}, definition.Changes.AllowedSellers...), 392 } 393 if err := assignSpotRiskModel(definition.Changes, market.TradableInstrument); err != nil { 394 return nil, types.ProposalErrorUnspecified, err 395 } 396 return market, types.ProposalErrorUnspecified, nil 397 } 398 399 func validateAssetBasic(assetID string, assets Assets, positionDecimals int64, deepCheck bool) (types.ProposalError, error) { 400 if len(assetID) <= 0 { 401 return types.ProposalErrorInvalidAsset, errors.New("missing asset ID") 402 } 403 404 if !deepCheck { 405 return types.ProposalErrorUnspecified, nil 406 } 407 408 as, err := assets.Get(assetID) 409 if err != nil { 410 return types.ProposalErrorInvalidAsset, err 411 } 412 if !assets.IsEnabled(assetID) { 413 return types.ProposalErrorInvalidAsset, 414 fmt.Errorf("asset is not enabled %v", assetID) 415 } 416 if positionDecimals > int64(as.DecimalPlaces()) { 417 return types.ProposalErrorInvalidSizeDecimalPlaces, fmt.Errorf("number of position decimal places must be less than or equal to the number base asset decimal places") 418 } 419 420 return types.ProposalErrorUnspecified, nil 421 } 422 423 func validateAsset(assetID string, decimals uint64, positionDecimals int64, assets Assets, deepCheck bool) (types.ProposalError, error) { 424 if len(assetID) <= 0 { 425 return types.ProposalErrorInvalidAsset, errors.New("missing asset ID") 426 } 427 428 if !deepCheck { 429 return types.ProposalErrorUnspecified, nil 430 } 431 432 asset, err := assets.Get(assetID) 433 if err != nil { 434 return types.ProposalErrorInvalidAsset, err 435 } 436 if !assets.IsEnabled(assetID) { 437 return types.ProposalErrorInvalidAsset, 438 fmt.Errorf("asset is not enabled %v", assetID) 439 } 440 if int64(decimals)+positionDecimals > int64(asset.DecimalPlaces()) { 441 return types.ProposalErrorTooManyMarketDecimalPlaces, errors.New("market decimal + position decimals must be less than or equal to asset decimals") 442 } 443 444 return types.ProposalErrorUnspecified, nil 445 } 446 447 func validateSpot(spot *types.SpotProduct, decimals uint64, positionDecimals int64, assets Assets, deepCheck bool) (types.ProposalError, error) { 448 propError, err := validateAsset(spot.QuoteAsset, decimals, positionDecimals, assets, deepCheck) 449 if err != nil { 450 return propError, err 451 } 452 return validateAssetBasic(spot.BaseAsset, assets, positionDecimals, deepCheck) 453 } 454 455 func validateFuture(future *types.FutureProduct, decimals uint64, positionDecimals int64, assets Assets, et *enactmentTime, deepCheck bool, evmChainIDs []uint64, tickSize *num.Uint) (types.ProposalError, error) { 456 future.DataSourceSpecForSettlementData = setDatasourceDefinitionDefaults(future.DataSourceSpecForSettlementData, et) 457 future.DataSourceSpecForTradingTermination = setDatasourceDefinitionDefaults(future.DataSourceSpecForTradingTermination, et) 458 459 settlData := &future.DataSourceSpecForSettlementData 460 if settlData == nil { 461 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForSettlementData 462 } 463 464 if !settlData.EnsureValidChainID(evmChainIDs) { 465 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 466 } 467 468 if settlData.Content() == nil { 469 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForSettlementData 470 } 471 472 ext, err := settlData.IsExternal() 473 if err != nil { 474 return types.ProposalErrorInvalidFutureProduct, err 475 } 476 477 if !ext { 478 return types.ProposalErrorInvalidFutureProduct, ErrSettlementWithInternalDataSourceIsNotAllowed 479 } 480 481 tterm := &future.DataSourceSpecForTradingTermination 482 if tterm == nil { 483 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForTradingTermination 484 } 485 486 if !tterm.EnsureValidChainID(evmChainIDs) { 487 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 488 } 489 490 if tterm.Content() == nil { 491 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForTradingTermination 492 } 493 494 tp, _ := tterm.Type() 495 if tp == datasource.ContentTypeInternalTimeTriggerTermination { 496 return types.ProposalErrorInvalidFutureProduct, ErrInternalTimeTriggerForFuturesInNotAllowed 497 } 498 499 filters := future.DataSourceSpecForTradingTermination.GetFilters() 500 for i, f := range filters { 501 if f.Key.Type == datapb.PropertyKey_TYPE_TIMESTAMP { 502 for j, cond := range f.Conditions { 503 v, err := strconv.ParseInt(cond.Value, 10, 64) 504 if err != nil { 505 return types.ProposalErrorInvalidFutureProduct, err 506 } 507 508 filters[i].Conditions[j].Value = strconv.FormatInt(v, 10) 509 if !et.shouldNotVerify { 510 if v <= et.current { 511 return types.ProposalErrorInvalidFutureProduct, ErrDataSourceSpecTerminationTimeBeforeEnactment 512 } 513 } 514 } 515 } 516 } 517 future.DataSourceSpecForTradingTermination.UpdateFilters(filters) 518 519 if future.DataSourceSpecBinding == nil { 520 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecBinding 521 } 522 523 // ensure the oracle spec for settlement data can be constructed 524 ospec, err := spec.New(*datasource.SpecFromDefinition(future.DataSourceSpecForSettlementData)) 525 if err != nil { 526 return types.ProposalErrorInvalidFutureProduct, err 527 } 528 switch future.DataSourceSpecBinding.SettlementDataProperty { 529 case datapb.PropertyKey_TYPE_DECIMAL.String(): 530 err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_DECIMAL) 531 if err != nil { 532 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 533 } 534 535 default: 536 err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_INTEGER) 537 if err != nil { 538 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 539 } 540 } 541 542 // ensure the oracle spec for market termination can be constructed 543 ospec, err = spec.New(*datasource.SpecFromDefinition(future.DataSourceSpecForTradingTermination)) 544 if err != nil { 545 return types.ProposalErrorInvalidFutureProduct, err 546 } 547 548 switch future.DataSourceSpecBinding.TradingTerminationProperty { 549 case spec.BuiltinTimestamp: 550 if err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.TradingTerminationProperty, datapb.PropertyKey_TYPE_TIMESTAMP); err != nil { 551 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for trading termination: %w", err) 552 } 553 default: 554 if err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.TradingTerminationProperty, datapb.PropertyKey_TYPE_BOOLEAN); err != nil { 555 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for trading termination: %w", err) 556 } 557 } 558 if err := validateFutureCap(future.Cap, tickSize); err != nil { 559 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid capped future configuration: %w", err) 560 } 561 562 return validateAsset(future.SettlementAsset, decimals, positionDecimals, assets, deepCheck) 563 } 564 565 func validateFutureCap(fCap *types.FutureCap, tickSize *num.Uint) error { 566 if fCap == nil { 567 return nil 568 } 569 if fCap.MaxPrice.IsZero() { 570 return ErrMaxPriceInvalid 571 } 572 // tick size of nil, zero, or one are fine for this check 573 mod := num.UintOne() 574 if tickSize == nil || tickSize.LTE(mod) { 575 return nil 576 } 577 // if maxPrice % tickSize != 0, the max price is invalid 578 if !mod.Mod(fCap.MaxPrice, tickSize).IsZero() { 579 return ErrMaxPriceInvalid 580 } 581 582 return nil 583 } 584 585 func validatePerps(perps *types.PerpsProduct, decimals uint64, positionDecimals int64, assets Assets, et *enactmentTime, currentTime time.Time, deepCheck bool, evmChainIDs []uint64) (types.ProposalError, error) { 586 perps.DataSourceSpecForSettlementData = setDatasourceDefinitionDefaults(perps.DataSourceSpecForSettlementData, et) 587 perps.DataSourceSpecForSettlementSchedule = setDatasourceDefinitionDefaults(perps.DataSourceSpecForSettlementSchedule, et) 588 589 settlData := &perps.DataSourceSpecForSettlementData 590 if settlData == nil { 591 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementData 592 } 593 594 if !settlData.EnsureValidChainID(evmChainIDs) { 595 return types.ProposalErrorInvalidPerpsProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 596 } 597 598 if settlData.Content() == nil { 599 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementData 600 } 601 602 ext, err := settlData.IsExternal() 603 if err != nil { 604 return types.ProposalErrorInvalidPerpsProduct, err 605 } 606 607 if !ext { 608 return types.ProposalErrorInvalidPerpsProduct, ErrSettlementWithInternalDataSourceIsNotAllowed 609 } 610 611 settlSchedule := &perps.DataSourceSpecForSettlementSchedule 612 if settlSchedule == nil { 613 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementSchedule 614 } 615 616 if !settlSchedule.EnsureValidChainID(evmChainIDs) { 617 return types.ProposalErrorInvalidPerpsProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 618 } 619 620 if settlSchedule.Content() == nil { 621 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementSchedule 622 } 623 624 if perps.DataSourceSpecBinding == nil { 625 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecBinding 626 } 627 628 // ensure the oracle spec for settlement data can be constructed 629 ospec, err := spec.New(*datasource.SpecFromDefinition(perps.DataSourceSpecForSettlementData)) 630 if err != nil { 631 return types.ProposalErrorInvalidPerpsProduct, err 632 } 633 switch perps.DataSourceSpecBinding.SettlementDataProperty { 634 case datapb.PropertyKey_TYPE_DECIMAL.String(): 635 err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_DECIMAL) 636 if err != nil { 637 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 638 } 639 default: 640 err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_INTEGER) 641 if err != nil { 642 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 643 } 644 } 645 646 // ensure the oracle spec for market termination can be constructed 647 ospec, err = spec.New(*datasource.SpecFromDefinition(perps.DataSourceSpecForSettlementSchedule)) 648 if err != nil { 649 return types.ProposalErrorInvalidPerpsProduct, err 650 } 651 652 switch perps.DataSourceSpecBinding.SettlementScheduleProperty { 653 case spec.BuiltinTimeTrigger: 654 tt := perps.DataSourceSpecForSettlementSchedule.GetInternalTimeTriggerSpecConfiguration() 655 if len(tt.Triggers) != 1 { 656 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid settlement schedule, only 1 trigger allowed") 657 } 658 659 if tt.Triggers[0] == nil { 660 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("at least 1 time trigger is required") 661 } 662 663 if tt.Triggers[0].Initial == nil { 664 tt.SetInitial(time.Unix(et.current, 0), currentTime) 665 } 666 tt.SetNextTrigger(currentTime) 667 668 // can't have the first trigger in the past 669 if tt.Triggers[0].Initial.Before(currentTime) { 670 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("time trigger starts in the past") 671 } 672 673 if err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementScheduleProperty, datapb.PropertyKey_TYPE_TIMESTAMP); err != nil { 674 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement schedule: %w", err) 675 } 676 default: 677 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("time trigger only supported for now") 678 } 679 680 if perps.InternalCompositePriceConfig != nil { 681 for _, v := range perps.InternalCompositePriceConfig.DataSources { 682 if !v.Data.EnsureValidChainID(evmChainIDs) { 683 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 684 } 685 } 686 } 687 688 return validateAsset(perps.SettlementAsset, decimals, positionDecimals, assets, deepCheck) 689 } 690 691 func validateNewInstrument(instrument *types.InstrumentConfiguration, decimals uint64, positionDecimals int64, assets Assets, et *enactmentTime, deepCheck bool, currentTime *time.Time, evmChainIDs []uint64, tickSize *num.Uint) (types.ProposalError, error) { 692 switch product := instrument.Product.(type) { 693 case nil: 694 return types.ProposalErrorNoProduct, ErrMissingProduct 695 case *types.InstrumentConfigurationFuture: 696 return validateFuture(product.Future, decimals, positionDecimals, assets, et, deepCheck, evmChainIDs, tickSize) 697 case *types.InstrumentConfigurationPerps: 698 return validatePerps(product.Perps, decimals, positionDecimals, assets, et, *currentTime, deepCheck, evmChainIDs) 699 case *types.InstrumentConfigurationSpot: 700 return validateSpot(product.Spot, decimals, positionDecimals, assets, deepCheck) 701 default: 702 return types.ProposalErrorUnsupportedProduct, ErrUnsupportedProduct 703 } 704 } 705 706 func validateLogNormalRiskParams(lnm *types.LogNormalRiskModel) (types.ProposalError, error) { 707 if lnm.Params == nil { 708 return types.ProposalErrorInvalidRiskParameter, ErrInvalidRiskParameter 709 } 710 711 if lnm.RiskAversionParameter.LessThan(num.DecimalFromFloat(1e-8)) || lnm.RiskAversionParameter.GreaterThan(num.DecimalFromFloat(0.1)) || // 1e-8 <= lambda <= 0.1 712 lnm.Tau.LessThan(num.DecimalFromFloat(1e-8)) || lnm.Tau.GreaterThan(num.DecimalOne()) || // 1e-8 <= tau <=1 713 lnm.Params.Mu.LessThan(num.DecimalFromFloat(-1e-6)) || lnm.Params.Mu.GreaterThan(num.DecimalFromFloat(1e-6)) || // -1e-6 <= mu <= 1e-6 714 lnm.Params.R.LessThan(num.DecimalFromInt64(-1)) || lnm.Params.R.GreaterThan(num.DecimalFromInt64(1)) || // -1 <= r <= 1 715 lnm.Params.Sigma.LessThan(num.DecimalFromFloat(1e-3)) || lnm.Params.Sigma.GreaterThan(num.DecimalFromInt64(50)) { // 1e-3 <= sigma <= 50 716 return types.ProposalErrorInvalidRiskParameter, ErrInvalidRiskParameter 717 } 718 return types.ProposalErrorUnspecified, nil 719 } 720 721 func validateRiskParameters(rp interface{}) (types.ProposalError, error) { 722 switch r := rp.(type) { 723 case *types.NewMarketConfigurationSimple: 724 return types.ProposalErrorUnspecified, nil 725 case *types.UpdateMarketConfigurationSimple: 726 return types.ProposalErrorUnspecified, nil 727 case *types.NewMarketConfigurationLogNormal: 728 return validateLogNormalRiskParams(r.LogNormal) 729 case *types.UpdateMarketConfigurationLogNormal: 730 return validateLogNormalRiskParams(r.LogNormal) 731 case *types.NewSpotMarketConfigurationSimple: 732 return types.ProposalErrorUnspecified, nil 733 case *types.UpdateSpotMarketConfigurationSimple: 734 return types.ProposalErrorUnspecified, nil 735 case *types.NewSpotMarketConfigurationLogNormal: 736 return validateLogNormalRiskParams(r.LogNormal) 737 case *types.UpdateSpotMarketConfigurationLogNormal: 738 return validateLogNormalRiskParams(r.LogNormal) 739 case nil: 740 return types.ProposalErrorNoRiskParameters, ErrMissingRiskParameters 741 default: 742 return types.ProposalErrorUnknownRiskParameterType, ErrUnsupportedRiskParameters 743 } 744 } 745 746 func validateLiquidationStrategy(ls *types.LiquidationStrategy) (types.ProposalError, error) { 747 if ls == nil { 748 // @TODO this will become a required parameter, but for now leave it as is 749 // this will be implemented in at a later stage 750 return types.ProposalErrorUnspecified, nil 751 } 752 if ls.DisposalFraction.IsZero() || ls.DisposalFraction.IsNegative() || ls.DisposalFraction.GreaterThan(num.DecimalOne()) { 753 return types.ProposalErrorInvalidMarket, fmt.Errorf("liquidation strategy disposal fraction must be in the 0-1 range and non-zero") 754 } 755 if ls.MaxFractionConsumed.IsZero() || ls.DisposalFraction.IsNegative() || ls.DisposalFraction.GreaterThan(num.DecimalOne()) { 756 return types.ProposalErrorInvalidMarket, fmt.Errorf("liquidation max fraction must be in the 0-1 range and non-zero") 757 } 758 if ls.DisposalTimeStep < time.Second { 759 return types.ProposalErrorInvalidMarket, fmt.Errorf("liquidation strategy time step has to be 1s or more") 760 } else if ls.DisposalTimeStep > time.Hour { 761 return types.ProposalErrorInvalidMarket, fmt.Errorf("liquidation strategy time step can't be more than 1h") 762 } 763 if ls.DisposalSlippage.IsZero() || ls.DisposalSlippage.IsNegative() { 764 return types.ProposalErrorInvalidMarket, fmt.Errorf("liquidation strategy must specify a disposal slippage range > 0") 765 } 766 return types.ProposalErrorUnspecified, nil 767 } 768 769 func validateLPSLAParams(slaParams *types.LiquiditySLAParams) (types.ProposalError, error) { 770 if slaParams == nil { 771 return types.ProposalErrorMissingSLAParams, fmt.Errorf("liquidity provision SLA must be provided") 772 } 773 if slaParams.PriceRange.IsZero() || slaParams.PriceRange.LessThan(num.DecimalZero()) || slaParams.PriceRange.GreaterThan(num.DecimalFromFloat(20)) { 774 return types.ProposalErrorInvalidSLAParams, fmt.Errorf("price range must be strictly greater than 0 and less than or equal to 20") 775 } 776 if slaParams.CommitmentMinTimeFraction.LessThan(num.DecimalZero()) || slaParams.CommitmentMinTimeFraction.GreaterThan(num.DecimalOne()) { 777 return types.ProposalErrorInvalidSLAParams, fmt.Errorf("commitment min time fraction must be in range [0, 1]") 778 } 779 if slaParams.SlaCompetitionFactor.LessThan(num.DecimalZero()) || slaParams.SlaCompetitionFactor.GreaterThan(num.DecimalOne()) { 780 return types.ProposalErrorInvalidSLAParams, fmt.Errorf("sla competition factor must be in range [0, 1]") 781 } 782 783 if slaParams.PerformanceHysteresisEpochs > 366 { 784 return types.ProposalErrorInvalidSLAParams, fmt.Errorf("provider performance hysteresis epochs must be less then 366") 785 } 786 return types.ProposalErrorUnspecified, nil 787 } 788 789 func validateAuctionDuration(proposedDuration time.Duration, netp NetParams) (types.ProposalError, error) { 790 minAuctionDuration, _ := netp.GetDuration(netparams.MarketAuctionMinimumDuration) 791 if proposedDuration < minAuctionDuration { 792 // Auction duration is too small 793 return types.ProposalErrorOpeningAuctionDurationTooSmall, 794 fmt.Errorf("proposal opening auction duration is too short, expected > %v, got %v", minAuctionDuration, proposedDuration) 795 } 796 maxAuctionDuration, _ := netp.GetDuration(netparams.MarketAuctionMaximumDuration) 797 if proposedDuration > maxAuctionDuration { 798 // Auction duration is too large 799 return types.ProposalErrorOpeningAuctionDurationTooLarge, 800 fmt.Errorf("proposal opening auction duration is too long, expected < %v, got %v", maxAuctionDuration, proposedDuration) 801 } 802 return types.ProposalErrorUnspecified, nil 803 } 804 805 func validateSlippageFactor(slippageFactor num.Decimal, isLinear bool) (types.ProposalError, error) { 806 err := types.ProposalErrorLinearSlippageOutOfRange 807 if !isLinear { 808 err = types.ProposalErrorQuadraticSlippageOutOfRange 809 } 810 if slippageFactor.IsNegative() { 811 return err, fmt.Errorf("proposal slippage factor has incorrect value, expected value in [0,1000000], got %s", slippageFactor.String()) 812 } 813 if slippageFactor.GreaterThan(num.DecimalFromInt64(1000000)) { 814 return err, fmt.Errorf("proposal slippage factor has incorrect value, expected value in [0,1000000], got %s", slippageFactor.String()) 815 } 816 return types.ProposalErrorUnspecified, nil 817 } 818 819 func getEVMChainIDs(netp NetParams) []uint64 { 820 ethCfg := &proto.EthereumConfig{} 821 if err := netp.GetJSONStruct(netparams.BlockchainsPrimaryEthereumConfig, ethCfg); err != nil { 822 panic(fmt.Sprintf("could not load ethereum config from network parameter, this should never happen: %v", err)) 823 } 824 cID, err := strconv.ParseUint(ethCfg.ChainId, 10, 64) 825 if err != nil { 826 panic(fmt.Sprintf("could not convert chain id from ethereum config into integer: %v", err)) 827 } 828 829 allIDs := []uint64{cID} 830 l2Cfgs := &proto.EthereumL2Configs{} 831 if err := netp.GetJSONStruct(netparams.BlockchainsEthereumL2Configs, l2Cfgs); err != nil { 832 panic(fmt.Sprintf("could not load ethereum l2 config from network parameter, this should never happen: %v", err)) 833 } 834 835 for _, v := range l2Cfgs.Configs { 836 l2ID, err := strconv.ParseUint(v.ChainId, 10, 64) 837 if err != nil { 838 panic(fmt.Sprintf("could not convert chain id from ethereum l2 config into integer: %v", err)) 839 } 840 allIDs = append(allIDs, l2ID) 841 } 842 843 return allIDs 844 } 845 846 func validateNewSpotMarketChange( 847 terms *types.NewSpotMarket, 848 assets Assets, 849 deepCheck bool, 850 netp NetParams, 851 openingAuctionDuration time.Duration, 852 etu *enactmentTime, 853 ) (types.ProposalError, error) { 854 if perr, err := validateNewInstrument(terms.Changes.Instrument, terms.Changes.PriceDecimalPlaces, terms.Changes.SizeDecimalPlaces, assets, etu, deepCheck, nil, getEVMChainIDs(netp), terms.Changes.TickSize); err != nil { 855 return perr, err 856 } 857 if perr, err := validateAuctionDuration(openingAuctionDuration, netp); err != nil { 858 return perr, err 859 } 860 if terms.Changes.PriceMonitoringParameters != nil && len(terms.Changes.PriceMonitoringParameters.Triggers) > 100 { 861 return types.ProposalErrorTooManyPriceMonitoringTriggers, 862 fmt.Errorf("%v price monitoring triggers set, maximum allowed is 100", len(terms.Changes.PriceMonitoringParameters.Triggers) > 100) 863 } 864 if perr, err := validateRiskParameters(terms.Changes.RiskParameters); err != nil { 865 return perr, err 866 } 867 if perr, err := validateLPSLAParams(terms.Changes.SLAParams); err != nil { 868 return perr, err 869 } 870 return types.ProposalErrorUnspecified, nil 871 } 872 873 // ValidateNewMarket checks new market proposal terms. 874 func validateNewMarketChange( 875 terms *types.NewMarket, 876 assets Assets, 877 deepCheck bool, 878 netp NetParams, 879 openingAuctionDuration time.Duration, 880 etu *enactmentTime, 881 parent *types.Market, 882 currentTime time.Time, 883 restore bool, 884 ) (types.ProposalError, error) { 885 // in all cases, the instrument must be specified and validated, successor markets included. 886 if perr, err := validateNewInstrument(terms.Changes.Instrument, terms.Changes.DecimalPlaces, terms.Changes.PositionDecimalPlaces, assets, etu, deepCheck, ptr.From(currentTime), getEVMChainIDs(netp), terms.Changes.TickSize); err != nil { 887 return perr, err 888 } 889 // verify opening auction duration, works the same for successor markets 890 if perr, err := validateAuctionDuration(openingAuctionDuration, netp); !etu.cpLoad && err != nil { 891 return perr, err 892 } 893 // if this is a successor market, check if that's set up fine: 894 if perr, err := validateSuccessorMarket(terms, parent, restore); err != nil { 895 return perr, err 896 } 897 if perr, err := validateRiskParameters(terms.Changes.RiskParameters); err != nil { 898 return perr, err 899 } 900 if terms.Changes.PriceMonitoringParameters != nil && len(terms.Changes.PriceMonitoringParameters.Triggers) > 100 { 901 return types.ProposalErrorTooManyPriceMonitoringTriggers, 902 fmt.Errorf("%v price monitoring triggers set, maximum allowed is 100", len(terms.Changes.PriceMonitoringParameters.Triggers) > 100) 903 } 904 if perr, err := validateLPSLAParams(terms.Changes.LiquiditySLAParameters); err != nil { 905 return perr, err 906 } 907 if perr, err := validateSlippageFactor(terms.Changes.LinearSlippageFactor, true); err != nil { 908 return perr, err 909 } 910 if perr, err := validateSlippageFactor(terms.Changes.QuadraticSlippageFactor, false); err != nil { 911 return perr, err 912 } 913 if terms.Changes.LiquidationStrategy == nil { 914 // @TODO At this stage, we don't require the liquidation strategy to be specified, treating nil as an implied legacy strategy. 915 terms.Changes.LiquidationStrategy = liquidation.GetLegacyStrat() 916 } else if perr, err := validateLiquidationStrategy(terms.Changes.LiquidationStrategy); err != nil { 917 return perr, err 918 } 919 920 if terms.Changes.MarkPriceConfiguration != nil { 921 for _, v := range terms.Changes.MarkPriceConfiguration.DataSources { 922 if !v.Data.EnsureValidChainID(getEVMChainIDs(netp)) { 923 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 924 } 925 } 926 } 927 928 return types.ProposalErrorUnspecified, nil 929 } 930 931 func validateSuccessorMarket(terms *types.NewMarket, parent *types.Market, restore bool) (types.ProposalError, error) { 932 suc := terms.Successor() 933 if (parent == nil && suc == nil) || (parent == nil && restore) { 934 return types.ProposalErrorUnspecified, nil 935 } 936 // if parent is not nil, then terms.Successor() was not nil and vice-versa. Either both are set or neither is. 937 if perr, err := validateInsurancePoolFraction(suc.InsurancePoolFraction); err != nil { 938 return perr, err 939 } 940 if perr, err := validateParentProduct(terms, parent); err != nil { 941 return perr, err 942 } 943 return types.ProposalErrorUnspecified, nil 944 } 945 946 func validateParentProduct(prop *types.NewMarket, parent *types.Market) (types.ProposalError, error) { 947 // make sure parent and successor are future markets 948 parentFuture := parent.GetFuture() 949 propFuture := prop.Changes.GetFuture() 950 if propFuture == nil || parentFuture == nil { 951 return types.ProposalErrorInvalidSuccessorMarket, fmt.Errorf("parent and successor markets must both be future markets") 952 } 953 if propFuture.Future.SettlementAsset != parentFuture.Future.SettlementAsset { 954 return types.ProposalErrorInvalidSuccessorMarket, fmt.Errorf("successor market must use asset %s", parentFuture.Future.SettlementAsset) 955 } 956 if propFuture.Future.QuoteName != parentFuture.Future.QuoteName { 957 return types.ProposalErrorInvalidSuccessorMarket, fmt.Errorf("successor market must use quote name %s", parentFuture.Future.QuoteName) 958 } 959 return types.ProposalErrorUnspecified, nil 960 } 961 962 func validateInsurancePoolFraction(frac num.Decimal) (types.ProposalError, error) { 963 one := num.DecimalFromInt64(1) 964 if frac.IsNegative() || frac.GreaterThan(one) { 965 return types.ProposalErrorInvalidSuccessorMarket, fmt.Errorf("insurance pool fraction should be in range 0-1, was %s", frac.String()) 966 } 967 return types.ProposalErrorUnspecified, nil 968 } 969 970 // validateUpdateMarketChange checks market update proposal terms. 971 func validateUpdateSpotMarketChange(terms *types.UpdateSpotMarket) (types.ProposalError, error) { 972 if perr, err := validateRiskParameters(terms.Changes.RiskParameters); err != nil { 973 return perr, err 974 } 975 if perr, err := validateLPSLAParams(terms.Changes.SLAParams); err != nil { 976 return perr, err 977 } 978 return types.ProposalErrorUnspecified, nil 979 } 980 981 // validateUpdateMarketChange checks market update proposal terms. 982 func validateUpdateMarketChange(terms *types.UpdateMarket, mkt types.Market, etu *enactmentTime, currentTime time.Time, netp NetParams) (types.ProposalError, error) { 983 if perr, err := validateUpdateInstrument(terms.Changes.Instrument, mkt, etu, currentTime, getEVMChainIDs(netp)); err != nil { 984 return perr, err 985 } 986 if perr, err := validateRiskParameters(terms.Changes.RiskParameters); err != nil { 987 return perr, err 988 } 989 if perr, err := validateLPSLAParams(terms.Changes.LiquiditySLAParameters); err != nil { 990 return perr, err 991 } 992 if perr, err := validateSlippageFactor(terms.Changes.LinearSlippageFactor, true); err != nil { 993 return perr, err 994 } 995 if perr, err := validateSlippageFactor(terms.Changes.QuadraticSlippageFactor, false); err != nil { 996 return perr, err 997 } 998 if perr, err := validateLiquidationStrategy(terms.Changes.LiquidationStrategy); err != nil { 999 return perr, err 1000 } 1001 return types.ProposalErrorUnspecified, nil 1002 } 1003 1004 func validateUpdateInstrument(instrument *types.UpdateInstrumentConfiguration, mkt types.Market, et *enactmentTime, currentTime time.Time, evmChainIDs []uint64) (types.ProposalError, error) { 1005 switch product := instrument.Product.(type) { 1006 case nil: 1007 return types.ProposalErrorNoProduct, ErrMissingProduct 1008 case *types.UpdateInstrumentConfigurationFuture: 1009 return validateUpdateFuture(product.Future, mkt, et, evmChainIDs) 1010 case *types.UpdateInstrumentConfigurationPerps: 1011 return validateUpdatePerps(product.Perps, mkt, et, currentTime, evmChainIDs) 1012 default: 1013 return types.ProposalErrorUnsupportedProduct, ErrUnsupportedProduct 1014 } 1015 } 1016 1017 func validateUpdateFuture(future *types.UpdateFutureProduct, mkt types.Market, et *enactmentTime, evmChainIDs []uint64) (types.ProposalError, error) { 1018 if mkt.GetFuture() == nil { 1019 return types.ProposalErrorInvalidFutureProduct, ErrUpdateMarketDifferentProduct 1020 } 1021 1022 future.DataSourceSpecForSettlementData = setDatasourceDefinitionDefaults(future.DataSourceSpecForSettlementData, et) 1023 future.DataSourceSpecForTradingTermination = setDatasourceDefinitionDefaults(future.DataSourceSpecForTradingTermination, et) 1024 1025 settlData := &future.DataSourceSpecForSettlementData 1026 if settlData == nil { 1027 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForSettlementData 1028 } 1029 1030 if !settlData.EnsureValidChainID(evmChainIDs) { 1031 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 1032 } 1033 1034 if settlData.Content() == nil { 1035 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForSettlementData 1036 } 1037 1038 ext, err := settlData.IsExternal() 1039 if err != nil { 1040 return types.ProposalErrorInvalidFutureProduct, err 1041 } 1042 1043 if !ext { 1044 return types.ProposalErrorInvalidFutureProduct, ErrSettlementWithInternalDataSourceIsNotAllowed 1045 } 1046 1047 tterm := &future.DataSourceSpecForTradingTermination 1048 if tterm == nil { 1049 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForTradingTermination 1050 } 1051 1052 if !tterm.EnsureValidChainID(evmChainIDs) { 1053 return types.ProposalErrorInvalidFutureProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 1054 } 1055 1056 if tterm.Content() == nil { 1057 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecForTradingTermination 1058 } 1059 1060 tp, _ := tterm.Type() 1061 if tp == datasource.ContentTypeInternalTimeTriggerTermination { 1062 return types.ProposalErrorInvalidFutureProduct, ErrInternalTimeTriggerForFuturesInNotAllowed 1063 } 1064 1065 filters := future.DataSourceSpecForTradingTermination.GetFilters() 1066 1067 for i, f := range filters { 1068 if f.Key.Type == datapb.PropertyKey_TYPE_TIMESTAMP { 1069 for j, cond := range f.Conditions { 1070 v, err := strconv.ParseInt(cond.Value, 10, 64) 1071 if err != nil { 1072 return types.ProposalErrorInvalidFutureProduct, err 1073 } 1074 1075 filters[i].Conditions[j].Value = strconv.FormatInt(v, 10) 1076 if !et.shouldNotVerify { 1077 if v <= et.current { 1078 return types.ProposalErrorInvalidFutureProduct, ErrDataSourceSpecTerminationTimeBeforeEnactment 1079 } 1080 } 1081 } 1082 } 1083 } 1084 1085 future.DataSourceSpecForTradingTermination.UpdateFilters(filters) 1086 1087 if future.DataSourceSpecBinding == nil { 1088 return types.ProposalErrorInvalidFutureProduct, ErrMissingDataSourceSpecBinding 1089 } 1090 1091 // ensure the oracle spec for settlement data can be constructed 1092 ospec, err := spec.New(*datasource.SpecFromDefinition(future.DataSourceSpecForSettlementData)) 1093 if err != nil { 1094 return types.ProposalErrorInvalidFutureProduct, err 1095 } 1096 switch future.DataSourceSpecBinding.SettlementDataProperty { 1097 case datapb.PropertyKey_TYPE_DECIMAL.String(): 1098 err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_DECIMAL) 1099 if err != nil { 1100 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 1101 } 1102 1103 default: 1104 err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_INTEGER) 1105 if err != nil { 1106 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 1107 } 1108 } 1109 1110 // ensure the oracle spec for market termination can be constructed 1111 ospec, err = spec.New(*datasource.SpecFromDefinition(future.DataSourceSpecForTradingTermination)) 1112 if err != nil { 1113 return types.ProposalErrorInvalidFutureProduct, err 1114 } 1115 1116 switch future.DataSourceSpecBinding.TradingTerminationProperty { 1117 case spec.BuiltinTimestamp: 1118 if err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.TradingTerminationProperty, datapb.PropertyKey_TYPE_TIMESTAMP); err != nil { 1119 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for trading termination: %w", err) 1120 } 1121 default: 1122 if err := ospec.EnsureBoundableProperty(future.DataSourceSpecBinding.TradingTerminationProperty, datapb.PropertyKey_TYPE_BOOLEAN); err != nil { 1123 return types.ProposalErrorInvalidFutureProduct, fmt.Errorf("invalid oracle spec binding for trading termination: %w", err) 1124 } 1125 } 1126 1127 return types.ProposalErrorUnspecified, nil 1128 } 1129 1130 func validateUpdatePerps(perps *types.UpdatePerpsProduct, mkt types.Market, et *enactmentTime, currentTime time.Time, evmChainIDs []uint64) (types.ProposalError, error) { 1131 if mkt.GetPerps() == nil { 1132 return types.ProposalErrorInvalidPerpsProduct, ErrUpdateMarketDifferentProduct 1133 } 1134 1135 perps.DataSourceSpecForSettlementData = setDatasourceDefinitionDefaults(perps.DataSourceSpecForSettlementData, et) 1136 perps.DataSourceSpecForSettlementSchedule = setDatasourceDefinitionDefaults(perps.DataSourceSpecForSettlementSchedule, et) 1137 1138 settlData := &perps.DataSourceSpecForSettlementData 1139 if settlData == nil { 1140 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementData 1141 } 1142 1143 if !settlData.EnsureValidChainID(evmChainIDs) { 1144 return types.ProposalErrorInvalidPerpsProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 1145 } 1146 1147 if settlData.Content() == nil { 1148 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementData 1149 } 1150 1151 ext, err := settlData.IsExternal() 1152 if err != nil { 1153 return types.ProposalErrorInvalidPerpsProduct, err 1154 } 1155 1156 if !ext { 1157 return types.ProposalErrorInvalidPerpsProduct, ErrSettlementWithInternalDataSourceIsNotAllowed 1158 } 1159 1160 settlSchedule := &perps.DataSourceSpecForSettlementSchedule 1161 if settlSchedule == nil { 1162 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementSchedule 1163 } 1164 1165 if !settlSchedule.EnsureValidChainID(evmChainIDs) { 1166 return types.ProposalErrorInvalidPerpsProduct, ErrInvalidEVMChainIDInEthereumOracleSpec 1167 } 1168 1169 if settlSchedule.Content() == nil { 1170 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecForSettlementSchedule 1171 } 1172 1173 if perps.DataSourceSpecBinding == nil { 1174 return types.ProposalErrorInvalidPerpsProduct, ErrMissingDataSourceSpecBinding 1175 } 1176 1177 // ensure the oracle spec for settlement data can be constructed 1178 ospec, err := spec.New(*datasource.SpecFromDefinition(perps.DataSourceSpecForSettlementData)) 1179 if err != nil { 1180 return types.ProposalErrorInvalidPerpsProduct, err 1181 } 1182 switch perps.DataSourceSpecBinding.SettlementDataProperty { 1183 case datapb.PropertyKey_TYPE_DECIMAL.String(): 1184 err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_DECIMAL) 1185 if err != nil { 1186 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 1187 } 1188 default: 1189 err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementDataProperty, datapb.PropertyKey_TYPE_INTEGER) 1190 if err != nil { 1191 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement data: %w", err) 1192 } 1193 } 1194 1195 // ensure the oracle spec for market termination can be constructed 1196 ospec, err = spec.New(*datasource.SpecFromDefinition(perps.DataSourceSpecForSettlementSchedule)) 1197 if err != nil { 1198 return types.ProposalErrorInvalidPerpsProduct, err 1199 } 1200 1201 switch perps.DataSourceSpecBinding.SettlementScheduleProperty { 1202 case spec.BuiltinTimeTrigger: 1203 tt := perps.DataSourceSpecForSettlementSchedule.GetInternalTimeTriggerSpecConfiguration() 1204 if len(tt.Triggers) != 1 { 1205 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid settlement schedule, only 1 trigger allowed") 1206 } 1207 1208 if tt.Triggers[0] == nil { 1209 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("at least 1 time trigger is required") 1210 } 1211 1212 if tt.Triggers[0].Initial == nil { 1213 tt.SetInitial(time.Unix(et.current, 0), currentTime) 1214 } 1215 tt.SetNextTrigger(currentTime) 1216 1217 // can't have the first trigger in the past, don't recheck if we've come in from preEnact 1218 if !et.shouldNotVerify && tt.Triggers[0].Initial.Before(currentTime) { 1219 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("time trigger starts in the past") 1220 } 1221 1222 if err := ospec.EnsureBoundableProperty(perps.DataSourceSpecBinding.SettlementScheduleProperty, datapb.PropertyKey_TYPE_TIMESTAMP); err != nil { 1223 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("invalid oracle spec binding for settlement schedule: %w", err) 1224 } 1225 default: 1226 return types.ProposalErrorInvalidPerpsProduct, fmt.Errorf("time trigger only supported for now") 1227 } 1228 1229 return types.ProposalErrorUnspecified, nil 1230 } 1231 1232 func setDatasourceDefinitionDefaults(def dsdefinition.Definition, et *enactmentTime) dsdefinition.Definition { 1233 if def.IsEthCallSpec() { 1234 spec := def.GetEthCallSpec() 1235 if spec.Trigger != nil { 1236 switch trigger := spec.Trigger.(type) { 1237 case ethcallcommon.TimeTrigger: 1238 if trigger.Initial == 0 { 1239 trigger.Initial = uint64(et.current) 1240 } 1241 spec.Trigger = trigger 1242 } 1243 } 1244 def.DataSourceType = spec 1245 } 1246 1247 return def 1248 }