code.vegaprotocol.io/vega@v0.79.0/core/products/oracles.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 products
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"code.vegaprotocol.io/vega/core/datasource"
    25  	"code.vegaprotocol.io/vega/core/datasource/spec"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    29  )
    30  
    31  type scheduledOracle struct {
    32  	settlementSubscriptionID spec.SubscriptionID
    33  	scheduleSubscriptionID   spec.SubscriptionID
    34  	binding                  scheduledBinding
    35  	settleUnsub              spec.Unsubscriber
    36  	scheduleUnsub            spec.Unsubscriber
    37  }
    38  
    39  type terminatingOracle struct {
    40  	settlementSubscriptionID  spec.SubscriptionID
    41  	terminationSubscriptionID spec.SubscriptionID
    42  	settleUnsub               spec.Unsubscriber
    43  	terminationUnsub          spec.Unsubscriber
    44  	binding                   terminatingBinding
    45  	data                      oracleData
    46  }
    47  
    48  type scheduledBinding struct {
    49  	settlementProperty string
    50  	settlementType     datapb.PropertyKey_Type
    51  	settlementDecimals uint64
    52  
    53  	scheduleProperty string
    54  	scheduleType     datapb.PropertyKey_Type
    55  }
    56  
    57  type terminatingBinding struct {
    58  	settlementProperty string
    59  	settlementType     datapb.PropertyKey_Type
    60  	settlementDecimals uint64
    61  
    62  	terminationProperty string
    63  	terminationType     datapb.PropertyKey_Type
    64  }
    65  
    66  type oracleData struct {
    67  	settlData         *num.Numeric
    68  	tradingTerminated bool
    69  }
    70  
    71  func newFutureOracle(f *types.Future) (terminatingOracle, error) {
    72  	bind, err := newFutureBinding(f)
    73  	if err != nil {
    74  		return terminatingOracle{}, err
    75  	}
    76  	return terminatingOracle{
    77  		binding: bind,
    78  	}, nil
    79  }
    80  
    81  func newFutureBinding(f *types.Future) (terminatingBinding, error) {
    82  	settlementProperty := strings.TrimSpace(f.DataSourceSpecBinding.SettlementDataProperty)
    83  	if len(settlementProperty) == 0 {
    84  		return terminatingBinding{}, errors.New("binding for settlement data cannot be blank")
    85  	}
    86  	tradingTerminationProperty := strings.TrimSpace(f.DataSourceSpecBinding.TradingTerminationProperty)
    87  	if len(tradingTerminationProperty) == 0 {
    88  		return terminatingBinding{}, errors.New("binding for trading termination market cannot be blank")
    89  	}
    90  	// assume bool for now, check for built-in timestamp
    91  	// this can be set to anything else by the caller
    92  	termType := datapb.PropertyKey_TYPE_BOOLEAN
    93  	if tradingTerminationProperty == spec.BuiltinTimestamp {
    94  		termType = datapb.PropertyKey_TYPE_TIMESTAMP
    95  	}
    96  	t, dec := getSettleTypeAndDec(f.DataSourceSpecForSettlementData)
    97  
    98  	return terminatingBinding{
    99  		settlementProperty:  settlementProperty,
   100  		settlementType:      t,
   101  		settlementDecimals:  dec,
   102  		terminationProperty: tradingTerminationProperty,
   103  		terminationType:     termType,
   104  	}, nil
   105  }
   106  
   107  func (t *terminatingOracle) bindAll(ctx context.Context, oe OracleEngine, settle, term *spec.Spec, settleCB, termCB spec.OnMatchedData) error {
   108  	if err := t.bindSettlement(ctx, oe, settle, settleCB); err != nil {
   109  		return err
   110  	}
   111  	return t.bindTermination(ctx, oe, term, termCB)
   112  }
   113  
   114  func (t *terminatingOracle) bindSettlement(ctx context.Context, oe OracleEngine, osForSettle *spec.Spec, cb spec.OnMatchedData) error {
   115  	err := osForSettle.EnsureBoundableProperty(t.binding.settlementProperty, t.binding.settlementType)
   116  	if err != nil {
   117  		return fmt.Errorf("invalid oracle spec binding for settlement data: %w", err)
   118  	}
   119  	if t.settlementSubscriptionID, t.settleUnsub, err = oe.Subscribe(ctx, *osForSettle, cb); err != nil {
   120  		return fmt.Errorf("could not subscribe to oracle engine for settlement data: %w", err)
   121  	}
   122  	return nil
   123  }
   124  
   125  func (t *terminatingOracle) bindTermination(ctx context.Context, oe OracleEngine, osForTerm *spec.Spec, cb spec.OnMatchedData) error {
   126  	err := osForTerm.EnsureBoundableProperty(t.binding.terminationProperty, t.binding.terminationType)
   127  	if err != nil {
   128  		return fmt.Errorf("invalid oracle spec binding for trading termination: %w", err)
   129  	}
   130  	if t.terminationSubscriptionID, t.terminationUnsub, err = oe.Subscribe(ctx, *osForTerm, cb); err != nil {
   131  		return fmt.Errorf("could not subscribe to oracle engine for trading termination: %w", err)
   132  	}
   133  	return nil
   134  }
   135  
   136  func (t *terminatingOracle) unsubSettle(ctx context.Context) {
   137  	if t.settleUnsub != nil {
   138  		t.settleUnsub(ctx, t.settlementSubscriptionID)
   139  		t.settleUnsub = nil
   140  	}
   141  }
   142  
   143  func (t *terminatingOracle) unsubTerm(ctx context.Context) {
   144  	if t.terminationUnsub != nil {
   145  		t.terminationUnsub(ctx, t.terminationSubscriptionID)
   146  		t.terminationUnsub = nil
   147  	}
   148  }
   149  
   150  func newPerpOracle(p *types.Perps) (scheduledOracle, error) {
   151  	bind, err := newPerpBinding(p)
   152  	if err != nil {
   153  		return scheduledOracle{}, err
   154  	}
   155  	return scheduledOracle{
   156  		binding: bind,
   157  	}, nil
   158  }
   159  
   160  func newPerpBinding(p *types.Perps) (scheduledBinding, error) {
   161  	settleDataProp := strings.TrimSpace(p.DataSourceSpecBinding.SettlementDataProperty)
   162  	settleScheduleProp := strings.TrimSpace(p.DataSourceSpecBinding.SettlementScheduleProperty)
   163  	if len(settleDataProp) == 0 {
   164  		return scheduledBinding{}, errors.New("binding for settlement data cannot be blank")
   165  	}
   166  	if len(settleScheduleProp) == 0 {
   167  		return scheduledBinding{}, errors.New("binding for settlement schedule cannot be blank")
   168  	}
   169  	setT, dec := getSettleTypeAndDec(p.DataSourceSpecForSettlementData)
   170  
   171  	return scheduledBinding{
   172  		settlementProperty: settleDataProp,
   173  		settlementType:     setT,
   174  		settlementDecimals: dec,
   175  		scheduleProperty:   settleScheduleProp,
   176  		scheduleType:       datapb.PropertyKey_TYPE_TIMESTAMP, // default to timestamp
   177  	}, nil
   178  }
   179  
   180  func (s *scheduledOracle) bindAll(ctx context.Context, oe OracleEngine, settle, schedule *spec.Spec, settleCB, scheduleCB spec.OnMatchedData) error {
   181  	if err := s.bindSettlement(ctx, oe, settle, settleCB); err != nil {
   182  		return err
   183  	}
   184  	return s.bindSchedule(ctx, oe, schedule, scheduleCB)
   185  }
   186  
   187  func (s *scheduledOracle) bindSettlement(ctx context.Context, oe OracleEngine, osForSettle *spec.Spec, cb spec.OnMatchedData) error {
   188  	err := osForSettle.EnsureBoundableProperty(s.binding.settlementProperty, s.binding.settlementType)
   189  	if err != nil {
   190  		return fmt.Errorf("invalid oracle spec binding for settlement data: %w", err)
   191  	}
   192  	if s.settlementSubscriptionID, s.settleUnsub, err = oe.Subscribe(ctx, *osForSettle, cb); err != nil {
   193  		return fmt.Errorf("could not subscribe to oracle engine for settlement data: %w", err)
   194  	}
   195  	return nil
   196  }
   197  
   198  func (s *scheduledOracle) bindSchedule(ctx context.Context, oe OracleEngine, osForSchedule *spec.Spec, cb spec.OnMatchedData) error {
   199  	err := osForSchedule.EnsureBoundableProperty(s.binding.scheduleProperty, s.binding.scheduleType)
   200  	if err != nil {
   201  		return fmt.Errorf("invalid  oracle spec binding for schedule data: %w", err)
   202  	}
   203  	if s.scheduleSubscriptionID, s.scheduleUnsub, err = oe.Subscribe(ctx, *osForSchedule, cb); err != nil {
   204  		return fmt.Errorf("could not subscribe to oracle engine for schedule data: %w", err)
   205  	}
   206  	return nil
   207  }
   208  
   209  func (s *scheduledOracle) unsubAll(ctx context.Context) {
   210  	if s.settleUnsub != nil {
   211  		s.settleUnsub(ctx, s.settlementSubscriptionID)
   212  		s.settleUnsub = nil
   213  	}
   214  	if s.scheduleUnsub != nil {
   215  		s.scheduleUnsub(ctx, s.scheduleSubscriptionID)
   216  		s.scheduleUnsub = nil
   217  	}
   218  }
   219  
   220  // SettlementData returns oracle data settlement data scaled as Uint.
   221  func (o *oracleData) SettlementData(op, ap uint32) (*num.Uint, error) {
   222  	if o.settlData.Decimal() == nil && o.settlData.Uint() == nil {
   223  		return nil, ErrDataSourceSettlementDataNotSet
   224  	}
   225  
   226  	if !o.settlData.SupportDecimalPlaces(int64(ap)) {
   227  		return nil, ErrSettlementDataDecimalsNotSupportedByAsset
   228  	}
   229  
   230  	// scale to given target decimals by multiplying by 10^(targetDP - oracleDP)
   231  	// if targetDP > oracleDP - this scales up the decimals of settlement data
   232  	// if targetDP < oracleDP - this scaled down the decimals of settlement data and can lead to loss of accuracy
   233  	// if there're equal - no scaling happens
   234  	return o.settlData.ScaleTo(int64(op), int64(ap))
   235  }
   236  
   237  // IsTradingTerminated returns true when oracle has signalled termination of trading.
   238  func (o *oracleData) IsTradingTerminated() bool {
   239  	return o.tradingTerminated
   240  }
   241  
   242  func getSettleTypeAndDec(s *datasource.Spec) (datapb.PropertyKey_Type, uint64) {
   243  	def := s.GetDefinition()
   244  	defType := datapb.PropertyKey_TYPE_INTEGER
   245  	dec := uint64(0)
   246  	for _, f := range def.GetFilters() {
   247  		if f.Key.Type == datapb.PropertyKey_TYPE_INTEGER && f.Key.NumberDecimalPlaces != nil {
   248  			dec = *f.Key.NumberDecimalPlaces
   249  			break
   250  		} else if f.Key.Type == datapb.PropertyKey_TYPE_DECIMAL {
   251  			defType = f.Key.Type
   252  			if f.Key.NumberDecimalPlaces != nil {
   253  				dec = *f.Key.NumberDecimalPlaces
   254  			}
   255  			break
   256  		}
   257  	}
   258  	return defType, dec
   259  }