code.vegaprotocol.io/vega@v0.79.0/core/volumerebate/snapshot.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 volumerebate
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/libs/proto"
    27  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    28  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    29  
    30  	"golang.org/x/exp/slices"
    31  )
    32  
    33  var (
    34  	key      = (&types.PayloadVolumeRebateProgram{}).Key()
    35  	hashKeys = []string{key}
    36  )
    37  
    38  type SnapshottedEngine struct {
    39  	*Engine
    40  
    41  	pl types.Payload
    42  }
    43  
    44  func (e *SnapshottedEngine) Namespace() types.SnapshotNamespace {
    45  	return types.VolumeRebateProgramSnapshot
    46  }
    47  
    48  func (e *SnapshottedEngine) Keys() []string {
    49  	return hashKeys
    50  }
    51  
    52  func (e *SnapshottedEngine) GetState(k string) ([]byte, []types.StateProvider, error) {
    53  	state, err := e.serialise(k)
    54  	return state, nil, err
    55  }
    56  
    57  func (e *Engine) loadCurrentProgramFromSnapshot(program *vegapb.VolumeRebateProgram) {
    58  	if program == nil {
    59  		e.currentProgram = nil
    60  		return
    61  	}
    62  
    63  	e.currentProgram = types.NewVolumeRebateProgramFromProto(program)
    64  }
    65  
    66  func (e *Engine) loadNewProgramFromSnapshot(program *vegapb.VolumeRebateProgram) {
    67  	if program == nil {
    68  		e.newProgram = nil
    69  		return
    70  	}
    71  
    72  	e.newProgram = types.NewVolumeRebateProgramFromProto(program)
    73  }
    74  
    75  func (e *SnapshottedEngine) restore(vdp *snapshotpb.VolumeRebateProgram) error {
    76  	e.latestProgramVersion = vdp.LastProgramVersion
    77  	e.programHasEnded = vdp.ProgramHasEnded
    78  	e.loadCurrentProgramFromSnapshot(vdp.CurrentProgram)
    79  	e.loadNewProgramFromSnapshot(vdp.NewProgram)
    80  	for _, v := range vdp.Parties {
    81  		e.parties[v] = struct{}{}
    82  	}
    83  	for _, pv := range vdp.PartyRebateData {
    84  		fraction, err := num.DecimalFromString(pv.Fraction)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		e.fractionPerParty[pv.Party] = fraction
    89  
    90  		makerFee, overflow := num.UintFromString(pv.MakerFeeReceived, 10)
    91  		if overflow {
    92  			return err
    93  		}
    94  		e.makerFeesReceivedInWindowPerParty[pv.Party] = makerFee
    95  	}
    96  
    97  	for _, stats := range vdp.FactorsByParty {
    98  		factor, err := num.DecimalFromString(stats.RebateFactor)
    99  		if err != nil {
   100  			return fmt.Errorf("could not parse string %q into decimal: %w", stats.RebateFactor, err)
   101  		}
   102  		e.factorsByParty[types.PartyID(stats.Party)] = types.VolumeRebateStats{
   103  			RebateFactor: factor,
   104  		}
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func (e *SnapshottedEngine) LoadState(_ context.Context, p *types.Payload) ([]types.StateProvider, error) {
   111  	if e.Namespace() != p.Data.Namespace() {
   112  		return nil, types.ErrInvalidSnapshotNamespace
   113  	}
   114  
   115  	switch data := p.Data.(type) {
   116  	case *types.PayloadVolumeRebateProgram:
   117  		return nil, e.restore(data.VolumeRebateProgram)
   118  	default:
   119  		return nil, types.ErrUnknownSnapshotType
   120  	}
   121  }
   122  
   123  func (e *SnapshottedEngine) Stopped() bool {
   124  	return false
   125  }
   126  
   127  func (e *SnapshottedEngine) serialise(k string) ([]byte, error) {
   128  	switch k {
   129  	case key:
   130  		return e.serialiseRebateVolumeProgram()
   131  	default:
   132  		return nil, types.ErrSnapshotKeyDoesNotExist
   133  	}
   134  }
   135  
   136  func (e *SnapshottedEngine) serialiseRebateVolumeProgram() ([]byte, error) {
   137  	parties := make([]string, 0, len(e.parties))
   138  	for pi := range e.parties {
   139  		parties = append(parties, pi)
   140  	}
   141  	sort.Strings(parties)
   142  
   143  	partyData := make([]*snapshotpb.PartyRebateData, 0, len(e.fractionPerParty))
   144  	for pi, d := range e.fractionPerParty {
   145  		partyData = append(partyData, &snapshotpb.PartyRebateData{
   146  			Party:            pi,
   147  			Fraction:         d.String(),
   148  			MakerFeeReceived: e.makerFeesReceivedInWindowPerParty[pi].String(),
   149  		})
   150  	}
   151  	sort.Slice(partyData, func(i, j int) bool {
   152  		return partyData[i].Party < partyData[j].Party
   153  	})
   154  
   155  	stats := make([]*snapshotpb.VolumeRebateStats, 0, len(e.factorsByParty))
   156  	for partyID, rebateStats := range e.factorsByParty {
   157  		stats = append(stats, &snapshotpb.VolumeRebateStats{
   158  			Party:        partyID.String(),
   159  			RebateFactor: rebateStats.RebateFactor.String(),
   160  		})
   161  	}
   162  	slices.SortStableFunc(stats, func(a, b *snapshotpb.VolumeRebateStats) int {
   163  		return strings.Compare(a.Party, b.Party)
   164  	})
   165  
   166  	payload := &snapshotpb.Payload{
   167  		Data: &snapshotpb.Payload_VolumeRebateProgram{
   168  			VolumeRebateProgram: &snapshotpb.VolumeRebateProgram{
   169  				CurrentProgram:     e.getProgram(e.currentProgram),
   170  				NewProgram:         e.getProgram(e.newProgram),
   171  				Parties:            parties,
   172  				PartyRebateData:    partyData,
   173  				FactorsByParty:     stats,
   174  				LastProgramVersion: e.latestProgramVersion,
   175  				ProgramHasEnded:    e.programHasEnded,
   176  			},
   177  		},
   178  	}
   179  	return proto.Marshal(payload)
   180  }
   181  
   182  func (e *SnapshottedEngine) getProgram(program *types.VolumeRebateProgram) *vegapb.VolumeRebateProgram {
   183  	if program == nil {
   184  		return nil
   185  	}
   186  	return program.IntoProto()
   187  }
   188  
   189  func NewSnapshottedEngine(broker Broker, mat MarketActivityTracker) *SnapshottedEngine {
   190  	se := &SnapshottedEngine{
   191  		Engine: New(broker, mat),
   192  		pl:     types.Payload{},
   193  	}
   194  
   195  	return se
   196  }