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 }