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  }