code.vegaprotocol.io/vega@v0.79.0/core/banking/fee_discount.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 banking
    17  
    18  import (
    19  	"context"
    20  	"sort"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  	"code.vegaprotocol.io/vega/logging"
    25  
    26  	"golang.org/x/exp/maps"
    27  )
    28  
    29  type partyAssetKey struct {
    30  	party string
    31  	asset string
    32  }
    33  
    34  func (e *Engine) feeDiscountKey(asset, party string) partyAssetKey {
    35  	return partyAssetKey{party: party, asset: asset}
    36  }
    37  
    38  func (e *Engine) applyPendingFeeDiscountsUpdates(ctx context.Context) {
    39  	assetIDs := maps.Keys(e.pendingPerAssetAndPartyFeeDiscountUpdates)
    40  	sort.Strings(assetIDs)
    41  
    42  	updatedKeys := map[string]map[string]struct{}{}
    43  	for _, assetID := range assetIDs {
    44  		feeDiscountsPerParty := e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID]
    45  		perAssetUpdatedKeys := e.updateFeeDiscountsForAsset(ctx, assetID, feeDiscountsPerParty)
    46  		updatedKeys[assetID] = perAssetUpdatedKeys
    47  	}
    48  
    49  	e.pendingPerAssetAndPartyFeeDiscountUpdates = map[string]map[string]*num.Uint{}
    50  	e.decayAllFeeDiscounts(ctx, updatedKeys)
    51  }
    52  
    53  func (e *Engine) decayAllFeeDiscounts(ctx context.Context, perAssetAndPartyUpdates map[string]map[string]struct{}) {
    54  	updateDiscountEvents := make([]events.Event, 0, len(e.feeDiscountPerPartyAndAsset))
    55  
    56  	feeDiscountPerPartyAndAssetKeys := maps.Keys(e.feeDiscountPerPartyAndAsset)
    57  	sort.SliceStable(feeDiscountPerPartyAndAssetKeys, func(i, j int) bool {
    58  		if feeDiscountPerPartyAndAssetKeys[i].party == feeDiscountPerPartyAndAssetKeys[j].party {
    59  			return feeDiscountPerPartyAndAssetKeys[i].asset < feeDiscountPerPartyAndAssetKeys[j].asset
    60  		}
    61  
    62  		return feeDiscountPerPartyAndAssetKeys[i].party < feeDiscountPerPartyAndAssetKeys[j].party
    63  	})
    64  
    65  	for _, key := range feeDiscountPerPartyAndAssetKeys {
    66  		if assetUpdate, assetOK := perAssetAndPartyUpdates[key.asset]; assetOK {
    67  			if _, partyOK := assetUpdate[key.party]; partyOK {
    68  				continue
    69  			}
    70  		}
    71  
    72  		// ensure asset exists
    73  		asset, err := e.assets.Get(key.asset)
    74  		if err != nil {
    75  			e.log.Panic("could not register taker fees, invalid asset", logging.Error(err))
    76  		}
    77  
    78  		assetQuantum := asset.Type().Details.Quantum
    79  
    80  		var decayAmountD *num.Uint
    81  		// apply decay discount amount
    82  		decayAmount := e.decayFeeDiscountAmount(e.feeDiscountPerPartyAndAsset[key])
    83  
    84  		// or 0 if discount is less than e.feeDiscountMinimumTrackedAmount x quantum (where quantum is the asset quantum).
    85  		if decayAmount.LessThan(e.feeDiscountMinimumTrackedAmount.Mul(assetQuantum)) {
    86  			decayAmountD = num.UintZero()
    87  			delete(e.feeDiscountPerPartyAndAsset, key)
    88  		} else {
    89  			decayAmountD, _ = num.UintFromDecimal(decayAmount)
    90  			e.feeDiscountPerPartyAndAsset[key] = decayAmountD
    91  		}
    92  
    93  		updateDiscountEvents = append(updateDiscountEvents, events.NewTransferFeesDiscountUpdated(
    94  			ctx,
    95  			key.party,
    96  			key.asset,
    97  			decayAmountD.Clone(),
    98  			e.currentEpoch,
    99  		))
   100  	}
   101  
   102  	if len(updateDiscountEvents) > 0 {
   103  		e.broker.SendBatch(updateDiscountEvents)
   104  	}
   105  }
   106  
   107  func (e *Engine) updateFeeDiscountsForAsset(
   108  	ctx context.Context, assetID string, feeDiscountsPerParty map[string]*num.Uint,
   109  ) map[string]struct{} {
   110  	updateDiscountEvents := make([]events.Event, 0, len(e.feeDiscountPerPartyAndAsset))
   111  
   112  	// ensure asset exists
   113  	asset, err := e.assets.Get(assetID)
   114  	if err != nil {
   115  		e.log.Panic("could not register taker fees, invalid asset", logging.Error(err))
   116  	}
   117  
   118  	assetQuantum := asset.Type().Details.Quantum
   119  
   120  	feesPerPartyKeys := maps.Keys(feeDiscountsPerParty)
   121  	sort.Strings(feesPerPartyKeys)
   122  
   123  	updatedKeys := map[string]struct{}{}
   124  	for _, party := range feesPerPartyKeys {
   125  		fee := feeDiscountsPerParty[party]
   126  
   127  		updatedKeys[party] = struct{}{}
   128  
   129  		key := e.feeDiscountKey(assetID, party)
   130  		if _, ok := e.feeDiscountPerPartyAndAsset[key]; !ok {
   131  			e.feeDiscountPerPartyAndAsset[key] = num.UintZero()
   132  		}
   133  
   134  		// apply decay discount amount and add new fees to it
   135  		newAmount := e.decayFeeDiscountAmount(e.feeDiscountPerPartyAndAsset[key]).Add(fee.ToDecimal())
   136  
   137  		if newAmount.LessThan(e.feeDiscountMinimumTrackedAmount.Mul(assetQuantum)) {
   138  			e.feeDiscountPerPartyAndAsset[key] = num.UintZero()
   139  		} else {
   140  			newAmountD, _ := num.UintFromDecimal(newAmount)
   141  			e.feeDiscountPerPartyAndAsset[key] = newAmountD
   142  		}
   143  
   144  		updateDiscountEvents = append(updateDiscountEvents, events.NewTransferFeesDiscountUpdated(
   145  			ctx,
   146  			party,
   147  			assetID,
   148  			e.feeDiscountPerPartyAndAsset[key].Clone(),
   149  			e.currentEpoch,
   150  		))
   151  	}
   152  
   153  	if len(updateDiscountEvents) > 0 {
   154  		e.broker.SendBatch(updateDiscountEvents)
   155  	}
   156  
   157  	return updatedKeys
   158  }
   159  
   160  func (e *Engine) RegisterTradingFees(ctx context.Context, assetID string, feesPerParty map[string]*num.Uint) {
   161  	// ensure asset exists
   162  	_, err := e.assets.Get(assetID)
   163  	if err != nil {
   164  		e.log.Panic("could not register taker fees, invalid asset", logging.Error(err))
   165  	}
   166  
   167  	if _, ok := e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID]; !ok {
   168  		e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID] = map[string]*num.Uint{}
   169  	}
   170  
   171  	for partyID, fee := range feesPerParty {
   172  		if _, ok := e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID][partyID]; !ok {
   173  			e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID][partyID] = fee.Clone()
   174  			continue
   175  		}
   176  		e.pendingPerAssetAndPartyFeeDiscountUpdates[assetID][partyID].AddSum(fee)
   177  	}
   178  }
   179  
   180  func (e *Engine) ApplyFeeDiscount(ctx context.Context, asset string, party string, fee *num.Uint) (discountedFee *num.Uint, discount *num.Uint) {
   181  	discountedFee, discount = e.EstimateFeeDiscount(asset, party, fee)
   182  	if discount.IsZero() {
   183  		return discountedFee, discount
   184  	}
   185  
   186  	key := e.feeDiscountKey(asset, party)
   187  	defer e.broker.Send(
   188  		events.NewTransferFeesDiscountUpdated(ctx,
   189  			party, asset,
   190  			e.feeDiscountPerPartyAndAsset[key].Clone(),
   191  			e.currentEpoch,
   192  		),
   193  	)
   194  
   195  	e.feeDiscountPerPartyAndAsset[key].Sub(e.feeDiscountPerPartyAndAsset[key], discount)
   196  
   197  	return discountedFee, discount
   198  }
   199  
   200  func (e *Engine) EstimateFeeDiscount(asset string, party string, fee *num.Uint) (discountedFee *num.Uint, discount *num.Uint) {
   201  	if fee.IsZero() {
   202  		return fee, num.UintZero()
   203  	}
   204  
   205  	key := e.feeDiscountKey(asset, party)
   206  	accumulatedDiscount, ok := e.feeDiscountPerPartyAndAsset[key]
   207  	if !ok {
   208  		return fee, num.UintZero()
   209  	}
   210  
   211  	return calculateDiscount(accumulatedDiscount, fee)
   212  }
   213  
   214  func (e *Engine) AvailableFeeDiscount(asset string, party string) *num.Uint {
   215  	key := e.feeDiscountKey(asset, party)
   216  
   217  	if discount, ok := e.feeDiscountPerPartyAndAsset[key]; ok {
   218  		return discount.Clone()
   219  	}
   220  
   221  	return num.UintZero()
   222  }
   223  
   224  // decayFeeDiscountAmount update current discount with: discount x e.feeDiscountDecayFraction.
   225  func (e *Engine) decayFeeDiscountAmount(currentDiscount *num.Uint) num.Decimal {
   226  	discount := currentDiscount.ToDecimal()
   227  	if discount.IsZero() {
   228  		return discount
   229  	}
   230  	return discount.Mul(e.feeDiscountDecayFraction)
   231  }
   232  
   233  func calculateDiscount(accumulatedDiscount, theoreticalFee *num.Uint) (discountedFee, discount *num.Uint) {
   234  	theoreticalFeeD := theoreticalFee.ToDecimal()
   235  	// min(accumulatedDiscount-theoreticalFee,0)
   236  	feeD := num.MinD(
   237  		accumulatedDiscount.ToDecimal().Sub(theoreticalFee.ToDecimal()),
   238  		num.DecimalZero(),
   239  	).Neg()
   240  
   241  	appliedDiscount, _ := num.UintFromDecimal(theoreticalFeeD.Sub(feeD))
   242  	// -fee
   243  	discountedFee, _ = num.UintFromDecimal(feeD)
   244  	return discountedFee, appliedDiscount
   245  }