code.vegaprotocol.io/vega@v0.79.0/core/execution/common/equity_shares.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 "fmt" 20 "sort" 21 22 "code.vegaprotocol.io/vega/core/types" 23 "code.vegaprotocol.io/vega/libs/num" 24 ) 25 26 // lp holds LiquidityProvider stake and avg values. 27 type lp struct { 28 stake num.Decimal 29 share num.Decimal 30 avg num.Decimal 31 vStake num.Decimal 32 } 33 34 // EquityShares module controls the Equity sharing algorithm described on the spec: 35 // https://github.com/vegaprotocol/product/blob/02af55e048a92a204e9ee7b7ae6b4475a198c7ff/specs/0042-setting-fees-and-rewarding-lps.md#calculating-liquidity-provider-equity-like-share 36 type EquityShares struct { 37 // mvp is the MarketValueProxy 38 mvp num.Decimal 39 r num.Decimal 40 41 totalVStake num.Decimal 42 totalPStake num.Decimal 43 // lps is a map of party id to lp (LiquidityProviders) 44 lps map[string]*lp 45 // used to restore own ELS from checkpoint 46 pendingLPs []*types.ELSShare 47 48 openingAuctionEnded bool 49 } 50 51 func NewEquityShares(mvp num.Decimal) *EquityShares { 52 return &EquityShares{ 53 mvp: mvp, 54 r: num.DecimalZero(), 55 totalPStake: num.DecimalZero(), 56 totalVStake: num.DecimalZero(), 57 lps: map[string]*lp{}, 58 } 59 } 60 61 func (es *EquityShares) GetLPSCount() int { 62 return len(es.lps) 63 } 64 65 func (es *EquityShares) InheritELS(shares []*types.ELSShare) { 66 for _, els := range shares { 67 if current, ok := es.lps[els.PartyID]; ok { 68 update, _ := num.UintFromDecimal(current.stake) 69 // update the totals: 70 es.totalPStake = es.totalPStake.Sub(current.stake).Add(els.SuppliedStake) 71 es.totalVStake = es.totalVStake.Sub(current.vStake).Add(els.VStake) 72 // make it look like the old ELS data was part of this market... 73 current.stake = els.SuppliedStake 74 current.vStake = els.VStake 75 current.avg = els.Avg 76 current.share = els.Share 77 // then treat the current commitment as a change to the commitment amount: 78 es.SetPartyStake(els.PartyID, update) 79 } 80 } 81 } 82 83 func (es *EquityShares) RestoreELS(shares []*types.ELSShare) { 84 es.pendingLPs = shares 85 } 86 87 func (es *EquityShares) RollbackParentELS() { 88 // get all current stakes 89 current := es.lps 90 // clear current state: 91 es.lps = make(map[string]*lp, len(current)) 92 es.totalPStake, es.totalVStake = num.DecimalZero(), num.DecimalZero() 93 // now add the commitments one by one as if they were just made 94 95 // make the iteration over parties deterministic 96 pids := make([]string, 0, len(current)) 97 for k := range current { 98 pids = append(pids, k) 99 } 100 sort.Strings(pids) 101 102 for _, pid := range pids { 103 els := current[pid] 104 update, _ := num.UintFromDecimal(els.stake) 105 es.SetPartyStake(pid, update) 106 } 107 if len(pids) > 0 { 108 es.ResetAvgToLP(pids[len(pids)-1]) 109 } 110 } 111 112 func (es *EquityShares) LpsToLiquidityProviderFeeShare(ls map[string]num.Decimal) []*types.LiquidityProviderFeeShare { 113 out := make([]*types.LiquidityProviderFeeShare, 0, len(es.lps)) 114 for k, v := range es.lps { 115 out = append(out, &types.LiquidityProviderFeeShare{ 116 Party: k, 117 EquityLikeShare: v.share.String(), 118 AverageEntryValuation: v.avg.String(), 119 AverageScore: ls[k].String(), 120 VirtualStake: v.vStake.StringFixed(16), 121 }) 122 } 123 124 // sort then so we produce the same output on all nodes 125 sort.SliceStable(out, func(i, j int) bool { 126 return out[i].Party < out[j].Party 127 }) 128 129 return out 130 } 131 132 // OpeningAuctionEnded signal to the EquityShare that the 133 // opening auction has ended. 134 func (es *EquityShares) OpeningAuctionEnded() { 135 // we should never call this twice 136 if es.openingAuctionEnded { 137 panic("market already left opening auction") 138 } 139 if len(es.pendingLPs) > 0 { 140 es.InheritELS(es.pendingLPs) 141 es.pendingLPs = nil 142 } 143 es.openingAuctionEnded = true 144 es.r = num.DecimalZero() 145 // force recalc of vStake, ensuring ELS will be set properly 146 es.UpdateVStake() 147 } 148 149 func (es *EquityShares) UpdateVStake() { 150 total := num.DecimalZero() 151 factor := num.DecimalFromFloat(1.0).Add(es.r) 152 recalc := false 153 for _, v := range es.lps { 154 vStake := num.MaxD(v.stake, v.vStake.Mul(factor)) 155 v.vStake = vStake 156 total = total.Add(vStake) 157 if !recalc && v.share.IsZero() { 158 recalc = true 159 } 160 } 161 if !total.Equal(es.totalVStake) { 162 // some vStake changed, force recalc of ELS values. 163 es.totalVStake = total 164 recalc = true 165 } 166 if recalc { 167 es.updateAllELS() 168 } 169 } 170 171 func (es *EquityShares) GetTotalVStake() num.Decimal { 172 return es.totalVStake 173 } 174 175 func (es *EquityShares) GetMarketGrowth() num.Decimal { 176 return es.r 177 } 178 179 func (es *EquityShares) AvgTradeValue(avg num.Decimal) *EquityShares { 180 if avg.IsZero() { 181 // if es.openingAuctionEnded { 182 // // this should not be possible IRL, however unit tests like amend_lp_orders 183 // // rely on the EndOpeningAuction call and can end opening auction without setting a price 184 // // ie -> end opening auction without a trade value 185 // panic("opening auction ended, and avg trade value hit zero somehow?") 186 // } 187 return es 188 } 189 if !es.mvp.IsZero() { 190 es.r = avg.Sub(es.mvp).Div(es.mvp) 191 } else { 192 es.r = num.DecimalZero() 193 } 194 es.UpdateVStake() 195 es.mvp = avg 196 return es 197 } 198 199 func (es *EquityShares) ResetAvgToLP(id string) { 200 avg := es.lps[id].avg 201 for _, lp := range es.lps { 202 lp.avg = avg 203 } 204 } 205 206 // SetPartyStake sets LP values for a given party. 207 func (es *EquityShares) SetPartyStake(id string, newStakeU *num.Uint) { 208 v, found := es.lps[id] 209 if newStakeU == nil || newStakeU.IsZero() { 210 if found { 211 es.totalVStake = es.totalVStake.Sub(v.vStake) 212 es.totalPStake = es.totalPStake.Sub(v.stake) 213 } 214 delete(es.lps, id) 215 return 216 } 217 newStake := num.DecimalFromUint(newStakeU) 218 if found && newStake.Equals(v.stake) { 219 // stake didn't change? there's nothing to do really 220 return 221 } 222 // first time we set the newStake and mvp as avg. 223 if !found { 224 // this is technically a delta == new stake -> calculate avg accordingly 225 v = &lp{} 226 es.lps[id] = v 227 es.updateAvgPosDelta(v, newStake, newStake) // delta == new stake 228 return 229 } 230 231 delta := newStake.Sub(v.stake) 232 if newStake.LessThan(v.stake) { 233 // commitment decreased 234 es.totalVStake = es.totalVStake.Sub(v.vStake) 235 es.totalPStake = es.totalPStake.Sub(v.stake) 236 // vStake * (newStake/oldStake) or vStake * (old_stake + (-delta))/old_stake 237 // e.g. 8000 => 5000 == vStakle * ((8000 + (-3000))/8000) == vStake * (5000/8000) 238 v.vStake = v.vStake.Mul(newStake.Div(v.stake)) 239 v.stake = newStake 240 es.totalVStake = es.totalVStake.Add(v.vStake) 241 es.totalPStake = es.totalPStake.Add(v.stake) 242 // recalculate ELS for this party 243 v.share, _ = es.equity(id) 244 return 245 } 246 247 es.updateAvgPosDelta(v, delta, newStake) 248 } 249 250 func (es *EquityShares) updateAvgPosDelta(v *lp, delta, newStake num.Decimal) { 251 // entry valuation == total Virtual stake (before delta is applied) 252 // (average entry valuation) <- (average entry valuation) x S / (S + Delta S) + (entry valuation) x (Delta S) / (S + Delta S) 253 // S being the LP's physical stake, Delta S being the amount by which the stake is increased 254 es.totalVStake = es.totalVStake.Add(delta) 255 es.totalPStake = es.totalPStake.Sub(v.stake).Add(newStake) 256 v.avg = v.avg.Mul(v.stake).Div(newStake).Add(es.totalVStake.Mul(delta).Div(newStake)) 257 // this is the first LP -> no vStake yet 258 v.vStake = v.vStake.Add(delta) 259 v.stake = newStake 260 } 261 262 // AvgEntryValuation returns the Average Entry Valuation for a given party. 263 func (es *EquityShares) AvgEntryValuation(id string) num.Decimal { 264 if v, ok := es.lps[id]; ok { 265 return v.avg 266 } 267 return num.DecimalZero() 268 } 269 270 // equity returns the following: 271 // (LP i stake)(n) x market_value_proxy(n) / (LP i avg_entry_valuation)(n) 272 // given a party id (i). 273 // 274 // Returns an error if the party has no stake. 275 func (es *EquityShares) equity(id string) (num.Decimal, error) { 276 if v, ok := es.lps[id]; ok { 277 if es.totalVStake.IsZero() { 278 return num.DecimalZero(), nil 279 } 280 return v.vStake.Div(es.totalVStake), nil 281 } 282 283 return num.DecimalZero(), fmt.Errorf("party %s has no stake", id) 284 } 285 286 // AllShares returns the ratio of equity for each party on the market. 287 func (es *EquityShares) AllShares() map[string]num.Decimal { 288 return es.SharesExcept(map[string]struct{}{}) 289 } 290 291 // SharesFromParty returns the equity-like shares of a given party on the market. 292 func (es *EquityShares) SharesFromParty(party string) num.Decimal { 293 totalEquity := num.DecimalZero() 294 partyELS := num.DecimalZero() 295 for id := range es.lps { 296 eq, err := es.equity(id) 297 if err != nil { 298 // since equity(id) returns an error when the party does not exist 299 // getting an error here means we are doing something wrong cause 300 // it should never happen unless `.equity()` behavior changes. 301 panic(err) 302 } 303 if id == party { 304 partyELS = eq 305 } 306 totalEquity = totalEquity.Add(eq) 307 } 308 309 if partyELS.Equal(num.DecimalZero()) || totalEquity.Equal(num.DecimalZero()) { 310 return num.DecimalZero() 311 } 312 313 return partyELS.Div(totalEquity) 314 } 315 316 // HasShares returns whether the given party is registered as an LP with ELS. 317 func (es *EquityShares) HasShares(party string) bool { 318 _, ok := es.lps[party] 319 return ok 320 } 321 322 // SharesExcept returns the ratio of equity for each party on the market, except 323 // the ones listed in parameter. 324 func (es *EquityShares) SharesExcept(except map[string]struct{}) map[string]num.Decimal { 325 shares := make(map[string]num.Decimal, len(es.lps)-len(except)) 326 allTotal, allTotalV := es.totalPStake, es.totalVStake 327 all := true 328 for k := range except { 329 if v, ok := es.lps[k]; ok { 330 es.totalPStake = es.totalPStake.Sub(v.stake) 331 es.totalVStake = es.totalVStake.Sub(v.vStake) 332 all = false 333 } 334 } 335 for id, v := range es.lps { 336 if _, ok := except[id]; ok { 337 continue 338 } 339 eq, err := es.equity(id) 340 if err != nil { 341 panic(err) 342 } 343 shares[id] = eq 344 if all && !v.share.Equals(eq) { 345 v.share = eq 346 } 347 } 348 if !all { 349 es.totalPStake = allTotal 350 es.totalVStake = allTotalV 351 } 352 return shares 353 } 354 355 func (es *EquityShares) updateAllELS() { 356 for id, v := range es.lps { 357 eq, err := es.equity(id) 358 if err != nil { 359 panic(err) 360 } 361 if !v.share.Equals(eq) { 362 v.share = eq 363 } 364 } 365 } 366 367 func (es *EquityShares) GetCPShares() []*types.ELSShare { 368 shares := make([]*types.ELSShare, 0, len(es.lps)) 369 for id, els := range es.lps { 370 shares = append(shares, &types.ELSShare{ 371 PartyID: id, 372 Share: els.share, 373 SuppliedStake: els.stake, 374 VStake: els.vStake, 375 Avg: els.avg, 376 }) 377 } 378 // make sure data is sorted 379 sort.SliceStable(shares, func(i, j int) bool { 380 return shares[i].PartyID > shares[j].PartyID 381 }) 382 return shares 383 } 384 385 func (es *EquityShares) SetCPShares(shares []*types.ELSShare) { 386 es.lps = make(map[string]*lp, len(shares)) 387 es.totalPStake = num.DecimalZero() 388 es.totalVStake = num.DecimalZero() 389 for _, share := range shares { 390 lp := lp{ 391 avg: share.Avg, 392 share: share.Share, 393 stake: share.SuppliedStake, 394 vStake: share.VStake, 395 } 396 es.totalPStake = es.totalPStake.Add(lp.stake) 397 es.totalVStake = es.totalVStake.Add(lp.vStake) 398 es.lps[share.PartyID] = &lp 399 } 400 }