code.vegaprotocol.io/vega@v0.79.0/core/execution/common/fees.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 common
    17  
    18  import (
    19  	"errors"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  )
    25  
    26  type FeeSplitter struct {
    27  	timeWindowStart time.Time
    28  	currentTime     time.Time
    29  	tradeValue      *num.Uint
    30  	changed         bool
    31  	avg             num.Decimal
    32  	window          uint64
    33  }
    34  
    35  func NewFeeSplitter() *FeeSplitter {
    36  	return &FeeSplitter{
    37  		tradeValue: num.UintZero(),
    38  		changed:    true,
    39  		window:     1, // initialise as 1 otherwise the average value calculation ends up being borked
    40  		avg:        num.DecimalZero(),
    41  	}
    42  }
    43  
    44  func (fs *FeeSplitter) SetCurrentTime(t time.Time) error {
    45  	if t.Before(fs.timeWindowStart) {
    46  		return errors.New("current time can't be before current window time")
    47  	}
    48  	// we're past the opening auction, or we have a trade value (ie we're leaving opening auction)
    49  	fs.currentTime = t
    50  	return nil
    51  }
    52  
    53  func (fs *FeeSplitter) TradeValue() *num.Uint {
    54  	return fs.tradeValue.Clone()
    55  }
    56  
    57  func (fs *FeeSplitter) AddTradeValue(tv *num.Uint) {
    58  	fs.tradeValue.AddSum(tv)
    59  	fs.changed = true
    60  }
    61  
    62  func (fs *FeeSplitter) SetTradeValue(tv *num.Uint) {
    63  	fs.tradeValue = tv.Clone()
    64  }
    65  
    66  // TimeWindowStart starts or restarts (if active) a current time window.
    67  // This sets the internal timers to `t` and resets the accumulated trade values.
    68  func (fs *FeeSplitter) TimeWindowStart(t time.Time) {
    69  	// if we have an average value, that means we left the opening auction
    70  	// and we can increase the window to the next value
    71  	if !fs.avg.IsZero() {
    72  		fs.window++
    73  	} else if !fs.tradeValue.IsZero() {
    74  		// if tradeValue is set, but the average hasn't been updated, it means we're currently leaving opening auction
    75  		// we should set the average accordingly: avg == trade_value, but keep the window as-is.
    76  		// next time we calculate the avg for window == 1, the value should be avg + trade_val (opening auction + trade value)
    77  		fs.avg = num.DecimalFromUint(fs.tradeValue)
    78  	}
    79  	// reset the trade value for this window
    80  	fs.tradeValue = num.UintZero()
    81  
    82  	// reset both timers
    83  	fs.timeWindowStart = t
    84  	fs.SetCurrentTime(t)
    85  	fs.changed = true
    86  }
    87  
    88  // Elapsed returns the distance (in duration) from TimeWindowStart and
    89  // CurrentTime.
    90  func (fs *FeeSplitter) Elapsed() time.Duration {
    91  	return fs.currentTime.Sub(fs.timeWindowStart)
    92  }
    93  
    94  func (fs *FeeSplitter) SetElapsed(e time.Duration) {
    95  	fs.timeWindowStart = fs.currentTime.Add(-e)
    96  }
    97  
    98  func (fs *FeeSplitter) activeWindowLength(mvw time.Duration) time.Duration {
    99  	t := fs.Elapsed()
   100  	return t - num.MaxV(t-mvw, 0)
   101  }
   102  
   103  // MarketValueProxy returns the market value proxy according to the spec:
   104  // https://github.com/vegaprotocol/product/blob/master/specs/0042-setting-fees-and-rewarding-lps.md
   105  func (fs *FeeSplitter) MarketValueProxy(mvwl time.Duration, totalStakeU *num.Uint) num.Decimal {
   106  	totalStake := num.DecimalFromUint(totalStakeU)
   107  	// t is the distance between
   108  	awl := fs.activeWindowLength(mvwl)
   109  	if awl > 0 {
   110  		factor := num.DecimalFromInt64(mvwl.Nanoseconds()).Div(
   111  			num.DecimalFromInt64(awl.Nanoseconds()))
   112  		tv := num.DecimalFromUint(fs.tradeValue)
   113  		return num.MaxD(totalStake, factor.Mul(tv))
   114  	}
   115  	return totalStake
   116  }
   117  
   118  func (fs *FeeSplitter) AvgTradeValue() num.Decimal {
   119  	tv := num.DecimalFromUint(fs.tradeValue)
   120  	// end of 1st window after opening auction
   121  	if fs.window == 1 {
   122  		fs.avg = fs.avg.Add(tv)
   123  		if !tv.IsZero() {
   124  			fs.changed = true
   125  		}
   126  		return fs.avg
   127  	}
   128  	fs.changed = true
   129  	// n == 2 or more
   130  	n := num.DecimalFromInt64(int64(fs.window))
   131  	// nmin == 1 or more
   132  	nmin := num.DecimalFromInt64(int64(fs.window - 1))
   133  	// avg = avg * ((n-1)/n) + tv/n
   134  	fs.avg = fs.avg.Mul(nmin.Div(n)).Add(tv.Div(n))
   135  	return fs.avg
   136  	// return tv
   137  }
   138  
   139  func NewFeeSplitterFromSnapshot(fs *types.FeeSplitter, now time.Time) *FeeSplitter {
   140  	return &FeeSplitter{
   141  		timeWindowStart: fs.TimeWindowStart,
   142  		currentTime:     now,
   143  		tradeValue:      fs.TradeValue,
   144  		changed:         true,
   145  		avg:             fs.Avg,
   146  		window:          fs.Window,
   147  	}
   148  }
   149  
   150  func (fs *FeeSplitter) GetState() *types.FeeSplitter {
   151  	fs.changed = false
   152  	return &types.FeeSplitter{
   153  		TimeWindowStart: fs.timeWindowStart,
   154  		TradeValue:      fs.tradeValue,
   155  		Avg:             fs.avg,
   156  		Window:          fs.window,
   157  	}
   158  }
   159  
   160  func (fs *FeeSplitter) Changed() bool {
   161  	return fs.changed
   162  }