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 }