code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/the_spot_markets.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 steps
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/collateral"
    24  	"code.vegaprotocol.io/vega/core/integration/steps/market"
    25  	"code.vegaprotocol.io/vega/core/netparams"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  
    29  	"github.com/cucumber/godog"
    30  )
    31  
    32  func TheSpotMarketsUpdated(
    33  	config *market.Config,
    34  	executionEngine Execution,
    35  	existing []types.Market,
    36  	netparams *netparams.Store,
    37  	table *godog.Table,
    38  ) ([]types.Market, error) {
    39  	rows := parseMarketsUpdateTable(table)
    40  	// existing markets to update
    41  	validByID := make(map[string]*types.Market, len(existing))
    42  	for i := range existing {
    43  		m := existing[i]
    44  		validByID[m.ID] = &existing[i]
    45  	}
    46  	updates := make([]types.UpdateSpotMarket, 0, len(rows))
    47  	updated := make([]*types.Market, 0, len(rows))
    48  	for _, row := range rows {
    49  		upd := spotMarketUpdateRow{row: row}
    50  		// check if market exists
    51  		current, ok := validByID[upd.id()]
    52  		if !ok {
    53  			return nil, fmt.Errorf("unknown market id %s", upd.id())
    54  		}
    55  		updates = append(updates, spotMarketUpdate(config, current, upd))
    56  		updated = append(updated, current)
    57  	}
    58  	if err := updateSpotMarkets(updated, updates, executionEngine); err != nil {
    59  		return nil, err
    60  	}
    61  	// we have been using pointers internally, so we should be returning the accurate state here.
    62  	return existing, nil
    63  }
    64  
    65  func updateSpotMarkets(markets []*types.Market, updates []types.UpdateSpotMarket, executionEngine Execution) error {
    66  	for i, mkt := range markets {
    67  		if err := executionEngine.UpdateSpotMarket(context.Background(), mkt); err != nil {
    68  			return fmt.Errorf("couldn't update market(%s) - updates %#v: %+v", mkt.ID, updates[i], err)
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  func TheSpotMarkets(config *market.Config, executionEngine Execution, collateralEngine *collateral.Engine, now time.Time, table *godog.Table) ([]types.Market, error) {
    75  	rows := parseSpotMarketsTable(table)
    76  	markets := make([]types.Market, 0, len(rows))
    77  
    78  	for _, row := range rows {
    79  		mkt := newSpotMarket(config, spotMarketRow{row: row})
    80  		markets = append(markets, mkt)
    81  	}
    82  
    83  	if err := enableSpotMarketAssets(markets, collateralEngine); err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	if err := enableVoteAsset(collateralEngine); err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	if err := submitSpotMarkets(markets, executionEngine, now); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return markets, nil
    96  }
    97  
    98  func submitSpotMarkets(markets []types.Market, executionEngine Execution, now time.Time) error {
    99  	for i := range markets {
   100  		if err := executionEngine.SubmitSpotMarket(context.Background(), &markets[i], "proposerID", now); err != nil {
   101  			return fmt.Errorf("couldn't submit market(%s): %v", markets[i].ID, err)
   102  		}
   103  		if err := executionEngine.StartOpeningAuction(context.Background(), markets[i].ID); err != nil {
   104  			return fmt.Errorf("could not start opening auction for market %s: %v", markets[i].ID, err)
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  func enableSpotMarketAssets(markets []types.Market, collateralEngine *collateral.Engine) error {
   111  	assetsToEnable := map[string]struct{}{}
   112  	for _, mkt := range markets {
   113  		assets, _ := mkt.GetAssets()
   114  		for _, asset := range assets {
   115  			assetsToEnable[asset] = struct{}{}
   116  		}
   117  	}
   118  	for assetToEnable := range assetsToEnable {
   119  		err := collateralEngine.EnableAsset(context.Background(), types.Asset{
   120  			ID: assetToEnable,
   121  			Details: &types.AssetDetails{
   122  				Quantum: num.DecimalOne(),
   123  				Symbol:  assetToEnable,
   124  			},
   125  		})
   126  		if err != nil && err != collateral.ErrAssetAlreadyEnabled {
   127  			return fmt.Errorf("couldn't enable asset(%s): %v", assetToEnable, err)
   128  		}
   129  	}
   130  	return nil
   131  }
   132  
   133  func (r spotMarketRow) liquidityMonitoring() string {
   134  	if !r.row.HasColumn("liquidity monitoring") {
   135  		return "default-parameters"
   136  	}
   137  	return r.row.MustStr("liquidity monitoring")
   138  }
   139  
   140  func newSpotMarket(config *market.Config, row spotMarketRow) types.Market {
   141  	fees, err := config.FeesConfig.Get(row.fees())
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  
   146  	priceMonitoring, err := config.PriceMonitoring.Get(row.priceMonitoring())
   147  	if err != nil {
   148  		panic(err)
   149  	}
   150  
   151  	slaParams, err := config.LiquiditySLAParams.Get(row.slaParams())
   152  	if err != nil {
   153  		panic(err)
   154  	}
   155  
   156  	liqMon, err := config.LiquidityMonitoring.GetType(row.liquidityMonitoring())
   157  	if err != nil {
   158  		panic(err)
   159  	}
   160  
   161  	m := types.Market{
   162  		TradingMode:           types.MarketTradingModeContinuous,
   163  		State:                 types.MarketStateActive,
   164  		ID:                    row.id(),
   165  		DecimalPlaces:         row.decimalPlaces(),
   166  		PositionDecimalPlaces: row.positionDecimalPlaces(),
   167  		Fees:                  types.FeesFromProto(fees),
   168  		TradableInstrument: &types.TradableInstrument{
   169  			Instrument: &types.Instrument{
   170  				ID:   fmt.Sprintf("Crypto/%s/Spots", row.id()),
   171  				Code: fmt.Sprintf("CRYPTO/%v", row.id()),
   172  				Name: fmt.Sprintf("%s spot", row.id()),
   173  				Metadata: &types.InstrumentMetadata{
   174  					Tags: []string{
   175  						"asset_class:spot/crypto",
   176  						"product:spots",
   177  					},
   178  				},
   179  				Product: &types.InstrumentSpot{
   180  					Spot: &types.Spot{
   181  						BaseAsset:  row.baseAsset(),
   182  						QuoteAsset: row.quoteAsset(),
   183  						Name:       row.name(),
   184  					},
   185  				},
   186  			},
   187  		},
   188  		OpeningAuction:                spotOpeningAuction(row),
   189  		PriceMonitoringSettings:       types.PriceMonitoringSettingsFromProto(priceMonitoring),
   190  		LiquidityMonitoringParameters: liqMon,
   191  		LiquiditySLAParams:            types.LiquiditySLAParamsFromProto(slaParams),
   192  		TickSize:                      row.tickSize(),
   193  	}
   194  
   195  	tip := m.TradableInstrument.IntoProto()
   196  	err = config.RiskModels.LoadModel(row.riskModel(), tip)
   197  	m.TradableInstrument = types.TradableInstrumentFromProto(tip)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  
   202  	return m
   203  }
   204  
   205  func spotMarketUpdate(config *market.Config, existing *types.Market, row spotMarketUpdateRow) types.UpdateSpotMarket {
   206  	update := types.UpdateSpotMarket{
   207  		MarketID: existing.ID,
   208  		Changes:  &types.UpdateSpotMarketConfiguration{},
   209  	}
   210  	// price monitoring
   211  	if pm, ok := row.priceMonitoring(); ok {
   212  		priceMonitoring, err := config.PriceMonitoring.Get(pm)
   213  		if err != nil {
   214  			panic(err)
   215  		}
   216  		pmt := types.PriceMonitoringSettingsFromProto(priceMonitoring)
   217  		// update existing
   218  		existing.PriceMonitoringSettings.Parameters = pmt.Parameters
   219  		update.Changes.PriceMonitoringParameters = pmt.Parameters
   220  	}
   221  	// liquidity monitoring
   222  	if lm, ok := row.liquidityMonitoring(); ok {
   223  		liqMon, err := config.LiquidityMonitoring.GetType(lm)
   224  		if err != nil {
   225  			panic(err)
   226  		}
   227  		existing.LiquidityMonitoringParameters = liqMon
   228  		update.Changes.TargetStakeParameters = liqMon.TargetStakeParameters
   229  	}
   230  
   231  	if sla, ok := row.slaParams(); ok {
   232  		slaParams, err := config.LiquiditySLAParams.Get(sla)
   233  		if err != nil {
   234  			panic(err)
   235  		}
   236  		existing.LiquiditySLAParams = types.LiquiditySLAParamsFromProto(slaParams)
   237  		update.Changes.SLAParams = types.LiquiditySLAParamsFromProto(slaParams)
   238  	}
   239  
   240  	update.Changes.LiquidityFeeSettings = existing.Fees.LiquidityFeeSettings
   241  	if liquidityFeeSettings, ok := row.tryLiquidityFeeSettings(); ok {
   242  		settings, err := config.FeesConfig.Get(liquidityFeeSettings)
   243  		if err != nil {
   244  			panic(err)
   245  		}
   246  		s := types.LiquidityFeeSettingsFromProto(settings.LiquidityFeeSettings)
   247  		existing.Fees.LiquidityFeeSettings = s
   248  		update.Changes.LiquidityFeeSettings = s
   249  	}
   250  
   251  	// risk model
   252  	if rm, ok := row.riskModel(); ok {
   253  		tip := existing.TradableInstrument.IntoProto()
   254  		if err := config.RiskModels.LoadModel(rm, tip); err != nil {
   255  			panic(err)
   256  		}
   257  		current := types.TradableInstrumentFromProto(tip)
   258  		// find the correct params:
   259  		switch {
   260  		case current.GetSimpleRiskModel() != nil:
   261  			update.Changes.RiskParameters = types.UpdateMarketConfigurationSimple{
   262  				Simple: current.GetSimpleRiskModel().Params,
   263  			}
   264  		case current.GetLogNormalRiskModel() != nil:
   265  			update.Changes.RiskParameters = types.UpdateMarketConfigurationLogNormal{
   266  				LogNormal: current.GetLogNormalRiskModel(),
   267  			}
   268  		default:
   269  			panic("Unsupported risk model parameters")
   270  		}
   271  		// update existing
   272  		existing.TradableInstrument = current
   273  	}
   274  	update.Changes.TickSize = row.tickSize()
   275  	return update
   276  }
   277  
   278  func spotOpeningAuction(row spotMarketRow) *types.AuctionDuration {
   279  	auction := &types.AuctionDuration{
   280  		Duration: row.auctionDuration(),
   281  	}
   282  
   283  	if auction.Duration <= 0 {
   284  		auction = nil
   285  	}
   286  	return auction
   287  }
   288  
   289  func parseSpotMarketsTable(table *godog.Table) []RowWrapper {
   290  	return StrictParseTable(table, []string{
   291  		"id",
   292  		"name",
   293  		"base asset",
   294  		"quote asset",
   295  		"risk model",
   296  		"fees",
   297  		"price monitoring",
   298  		"auction duration",
   299  		"sla params",
   300  	}, []string{
   301  		"decimal places",
   302  		"position decimal places",
   303  		"tick size",
   304  		"liquidity monitoring",
   305  	})
   306  }
   307  
   308  type spotMarketRow struct {
   309  	row RowWrapper
   310  }
   311  
   312  func (r spotMarketRow) id() string {
   313  	return r.row.MustStr("id")
   314  }
   315  
   316  func (r spotMarketRow) decimalPlaces() uint64 {
   317  	if !r.row.HasColumn("decimal places") {
   318  		return 0
   319  	}
   320  	return r.row.MustU64("decimal places")
   321  }
   322  
   323  func (r spotMarketRow) positionDecimalPlaces() int64 {
   324  	if !r.row.HasColumn("position decimal places") {
   325  		return 0
   326  	}
   327  	return r.row.MustI64("position decimal places")
   328  }
   329  
   330  func (r spotMarketRow) name() string {
   331  	return r.row.MustStr("name")
   332  }
   333  
   334  func (r spotMarketRow) baseAsset() string {
   335  	return r.row.MustStr("base asset")
   336  }
   337  
   338  func (r spotMarketRow) quoteAsset() string {
   339  	return r.row.MustStr("quote asset")
   340  }
   341  
   342  func (r spotMarketRow) riskModel() string {
   343  	return r.row.MustStr("risk model")
   344  }
   345  
   346  func (r spotMarketRow) tickSize() *num.Uint {
   347  	if r.row.HasColumn("tick size") {
   348  		return num.MustUintFromString(r.row.MustStr("tick size"), 10)
   349  	}
   350  	return num.UintOne()
   351  }
   352  
   353  func (r spotMarketRow) fees() string {
   354  	return r.row.MustStr("fees")
   355  }
   356  
   357  func (r spotMarketRow) priceMonitoring() string {
   358  	return r.row.MustStr("price monitoring")
   359  }
   360  
   361  func (r spotMarketRow) auctionDuration() int64 {
   362  	return r.row.MustI64("auction duration")
   363  }
   364  
   365  func (r spotMarketRow) slaParams() string {
   366  	return r.row.MustStr("sla params")
   367  }
   368  
   369  type spotMarketUpdateRow struct {
   370  	row RowWrapper
   371  }
   372  
   373  func (r spotMarketUpdateRow) id() string {
   374  	return r.row.MustStr("id")
   375  }
   376  
   377  func (r spotMarketUpdateRow) tickSize() *num.Uint {
   378  	if r.row.HasColumn("tick size") {
   379  		return num.MustUintFromString(r.row.MustStr("tick size"), 10)
   380  	}
   381  	return num.UintOne()
   382  }
   383  
   384  func (r spotMarketUpdateRow) priceMonitoring() (string, bool) {
   385  	if r.row.HasColumn("price monitoring") {
   386  		pm := r.row.MustStr("price monitoring")
   387  		return pm, true
   388  	}
   389  	return "", false
   390  }
   391  
   392  func (r spotMarketUpdateRow) riskModel() (string, bool) {
   393  	if r.row.HasColumn("risk model") {
   394  		rm := r.row.MustStr("risk model")
   395  		return rm, true
   396  	}
   397  	return "", false
   398  }
   399  
   400  func (r spotMarketUpdateRow) liquidityMonitoring() (string, bool) {
   401  	if r.row.HasColumn("liquidity monitoring") {
   402  		lm := r.row.MustStr("liquidity monitoring")
   403  		return lm, true
   404  	}
   405  	return "", false
   406  }
   407  
   408  func (r spotMarketUpdateRow) tryLiquidityFeeSettings() (string, bool) {
   409  	if r.row.HasColumn("liquidity fee settings") {
   410  		s := r.row.MustStr("liquidity fee settings")
   411  		return s, true
   412  	}
   413  	return "", false
   414  }
   415  
   416  func (r spotMarketUpdateRow) slaParams() (string, bool) {
   417  	if r.row.HasColumn("sla params") {
   418  		lm := r.row.MustStr("sla params")
   419  		return lm, true
   420  	}
   421  	return "", false
   422  }