code.vegaprotocol.io/vega@v0.79.0/core/execution/common/equity_shares_test.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_test 17 18 import ( 19 "fmt" 20 "testing" 21 22 "code.vegaprotocol.io/vega/core/execution/common" 23 "code.vegaprotocol.io/vega/libs/crypto" 24 "code.vegaprotocol.io/vega/libs/num" 25 "code.vegaprotocol.io/vega/libs/proto" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 ) 30 31 // just a convenience type used in some tests. 32 type lpdata struct { 33 id string 34 amt *num.Uint 35 avg num.Decimal 36 } 37 38 func TestEquityShares(t *testing.T) { 39 t.Run("AvgEntryValuation with trade value", testAvgEntryValuationGrowth) 40 t.Run("SharesExcept", testShares) 41 t.Run("Average entry valuation after 6063 spec change", testAvgEntryUpdate) 42 } 43 44 // replicate the example given in spec file (protocol/0042-LIQF-setting_fees_and_rewarding_lps.md). 45 func testAvgEntryUpdate(t *testing.T) { 46 es := common.NewEquityShares(num.DecimalZero()) 47 es.OpeningAuctionEnded() 48 initial := lpdata{ 49 id: "initial", 50 amt: num.NewUint(900), 51 avg: num.DecimalFromFloat(900), 52 } 53 es.SetPartyStake(initial.id, initial.amt) 54 require.True(t, initial.avg.Equals(es.AvgEntryValuation(initial.id)), es.AvgEntryValuation(initial.id).String()) 55 // step 1 from the example: LP commitment of 100 with an existing commitment of 1k: 56 step1 := lpdata{ 57 id: "step1", 58 amt: num.NewUint(100), 59 avg: num.NewDecimalFromFloat(1000), 60 } 61 es.SetPartyStake(step1.id, step1.amt) 62 require.True(t, step1.avg.Equals(es.AvgEntryValuation(step1.id)), es.AvgEntryValuation(step1.id).String()) 63 // get sum of all vStake to 2K as per example in the spec 64 // total vStake == 1.1k => avg is now 1.1k 65 inc := lpdata{ 66 id: "topup", 67 amt: num.NewUint(990), 68 avg: num.DecimalFromFloat(1990), 69 } 70 es.SetPartyStake(inc.id, inc.amt) 71 require.True(t, inc.avg.Equals(es.AvgEntryValuation(inc.id)), es.AvgEntryValuation(inc.id).String()) 72 // Example 2: We have a total vStake of 2k -> step1 party increases the commitment amount to 110 (so +10) 73 step1.amt = num.NewUint(110) 74 step1.avg, _ = num.DecimalFromString("1090.9090909090909091") 75 es.SetPartyStake(step1.id, step1.amt) 76 require.True(t, step1.avg.Equals(es.AvgEntryValuation(step1.id)), es.AvgEntryValuation(step1.id).String()) 77 // increase total vStake to be 3k using a new LP party 78 testAvgEntryUpdateStep3New(t, es) 79 // example 3 when total vStake is 3k -> decrease commitment by 20 80 step1.amt = num.NewUint(90) 81 es.SetPartyStake(step1.id, step1.amt) 82 require.True(t, step1.avg.Equals(es.AvgEntryValuation(step1.id)), es.AvgEntryValuation(step1.id).String()) 83 // set up example 3 again, this time by increasing the commitment of an existing party, their AEV should be updated accordingly 84 testAvgEntryUpdateStep3Add(t, es, &initial) 85 step1.amt = num.NewUint(70) // decrease by another 20 86 es.SetPartyStake(step1.id, step1.amt) 87 require.True(t, step1.avg.Equals(es.AvgEntryValuation(step1.id)), es.AvgEntryValuation(step1.id).String()) 88 // set up for example 3, this time by increasing the total vStake to 3k using growth 89 testAvgEntryUpdateStep3Growth(t, es) 90 step1.amt = num.NewUint(50) // decrease by another 20 91 es.SetPartyStake(step1.id, step1.amt) 92 require.True(t, step1.avg.Equals(es.AvgEntryValuation(step1.id)), es.AvgEntryValuation(step1.id).String()) 93 } 94 95 // continue based on testAvgEntryUpdate setup, just add new LP to get the total up to 3k. 96 func testAvgEntryUpdateStep3New(t *testing.T, es *common.EquityShares) { 97 t.Helper() 98 // we have 1000 + 110 + 990 (2000) 99 // AEV == 2000 100 inc := lpdata{ 101 id: "another", 102 amt: num.NewUint(1000), 103 avg: num.DecimalFromFloat(3000), 104 } 105 es.SetPartyStake(inc.id, inc.amt) 106 require.True(t, inc.avg.Equals(es.AvgEntryValuation(inc.id)), es.AvgEntryValuation(inc.id).String()) 107 } 108 109 func testAvgEntryUpdateStep3Add(t *testing.T, es *common.EquityShares, inc *lpdata) { 110 t.Helper() 111 // at this point, the total vStake is 2980, get it back up to 3k 112 // calc for delta 10: (average entry valuation) x S / (S + Delta S) + (entry valuation) x (Delta S) / (S + Delta S) 113 // using LP0 => 900 * 900 / 920 + 2980 * 20 / 920 == 945.6521739130434783 114 inc.amt.Add(inc.amt, num.NewUint(20)) 115 inc.avg, _ = num.DecimalFromString("945.6521739130434783") 116 es.SetPartyStake(inc.id, inc.amt) 117 require.True(t, inc.avg.Equals(es.AvgEntryValuation(inc.id)), es.AvgEntryValuation(inc.id).String()) 118 } 119 120 func testAvgEntryUpdateStep3Growth(t *testing.T, es *common.EquityShares) { 121 t.Helper() 122 // first, set the initial avg trade value 123 val := num.DecimalFromFloat(1000000) // 1 million 124 es.AvgTradeValue(val) 125 vStake := num.DecimalFromFloat(2980) 126 delta := num.DecimalFromFloat(20) 127 factor := delta.Div(vStake) 128 val = val.Add(factor.Mul(val)) // increase the value by 20/total_v_stake * previous value => growth rate should increase vStake back up to 3k 129 // this actually is going to set total vStake to 3000.000000000000136. Not perfect, but it's pretty close 130 es.AvgTradeValue(val) 131 } 132 133 func testAvgEntryValuationGrowth(t *testing.T) { 134 es := common.NewEquityShares(num.DecimalZero()) 135 tradeVal := num.DecimalFromFloat(1000) 136 lps := []lpdata{ 137 { 138 id: "LP1", 139 amt: num.NewUint(100), 140 avg: num.DecimalFromFloat(100), 141 }, 142 { 143 id: "LP2", 144 amt: num.NewUint(200), 145 avg: num.DecimalFromFloat(300), 146 }, 147 } 148 149 for _, l := range lps { 150 es.SetPartyStake(l.id, l.amt) 151 require.True(t, l.avg.Equals(es.AvgEntryValuation(l.id)), es.AvgEntryValuation(l.id).String()) 152 } 153 es.OpeningAuctionEnded() 154 155 // lps[1].avg = num.DecimalFromFloat(100) 156 // set trade value at auction end 157 es.AvgTradeValue(tradeVal) 158 for _, l := range lps { 159 aev := es.AvgEntryValuation(l.id) 160 require.True(t, l.avg.Equals(es.AvgEntryValuation(l.id)), fmt.Sprintf("FAIL ==> expected %s, got %s", l.avg, aev)) 161 } 162 163 // growth 164 tradeVal = num.DecimalFromFloat(1100) 165 // aev1, _ := num.DecimalFromString("100.000000000000001") 166 // lps[1].avg = aev1.Add(aev1) // double 167 es.AvgTradeValue(tradeVal) 168 for _, l := range lps { 169 aev := es.AvgEntryValuation(l.id) 170 require.True(t, l.avg.Equals(es.AvgEntryValuation(l.id)), fmt.Sprintf("FAIL => expected %s, got %s", l.avg, aev)) 171 } 172 lps[1].amt = num.NewUint(150) // reduce LP 173 es.SetPartyStake(lps[1].id, lps[1].amt) 174 for _, l := range lps { 175 aev := es.AvgEntryValuation(l.id) 176 require.True(t, l.avg.Equals(es.AvgEntryValuation(l.id)), fmt.Sprintf("FAIL => expected %s, got %s", l.avg, aev)) 177 } 178 // now simulate negative growth (ie r == 0) 179 tradeVal = num.DecimalFromFloat(1000) 180 es.AvgTradeValue(tradeVal) 181 // avg should line up with physical stake once more 182 // lps[1].avg = num.DecimalFromFloat(150) 183 for _, l := range lps { 184 aev := es.AvgEntryValuation(l.id) 185 require.True(t, l.avg.Equals(es.AvgEntryValuation(l.id)), fmt.Sprintf("FAIL => expected %s, got %s", l.avg, aev)) 186 } 187 } 188 189 func testShares(t *testing.T) { 190 one, two, three := num.DecimalFromFloat(1), num.DecimalFromFloat(2), num.DecimalFromFloat(3) 191 four, six := two.Mul(two), three.Mul(two) 192 var ( 193 oneSixth = one.Div(six) 194 oneThird = one.Div(three) 195 oneFourth = one.Div(four) 196 threeFourth = three.Div(four) 197 twoThirds = two.Div(three) 198 half = one.Div(two) 199 ) 200 201 es := common.NewEquityShares(num.DecimalFromFloat(100)) 202 203 // Set LP1 204 es.SetPartyStake("LP1", num.NewUint(100)) 205 t.Run("LP1", func(t *testing.T) { 206 s := es.SharesExcept(map[string]struct{}{}) 207 assert.True(t, one.Equal(s["LP1"])) 208 }) 209 210 // Set LP2 211 es.SetPartyStake("LP2", num.NewUint(200)) 212 t.Run("LP2", func(t *testing.T) { 213 s := es.SharesExcept(map[string]struct{}{}) 214 lp1, lp2 := s["LP1"], s["LP2"] 215 216 assert.Equal(t, oneThird, lp1) 217 assert.Equal(t, twoThirds, lp2) 218 assert.True(t, one.Equal(lp1.Add(lp2))) 219 }) 220 221 // Set LP3 222 es.SetPartyStake("LP3", num.NewUint(300)) 223 t.Run("LP3", func(t *testing.T) { 224 s := es.SharesExcept(map[string]struct{}{}) 225 226 lp1, lp2, lp3 := s["LP1"], s["LP2"], s["LP3"] 227 228 assert.Equal(t, oneSixth, lp1) 229 assert.Equal(t, oneThird, lp2) 230 assert.Equal(t, half, lp3) 231 assert.True(t, one.Equal(lp1.Add(lp2).Add(lp3))) 232 }) 233 234 // LP2 is undeployed 235 t.Run("LP3", func(t *testing.T) { 236 // pass LP as undeployed 237 s := es.SharesExcept(map[string]struct{}{"LP2": {}}) 238 239 lp1, lp3 := s["LP1"], s["LP3"] 240 _, ok := s["LP2"] 241 assert.False(t, ok) 242 243 assert.Equal(t, oneFourth, lp1) 244 // assert.Equal(t, oneThird, lp2) 245 assert.Equal(t, threeFourth, lp3) 246 assert.True(t, one.Equal(lp1.Add(lp3))) 247 }) 248 } 249 250 func getHash(es *common.EquityShares) []byte { 251 state := es.GetState() 252 esproto := state.IntoProto() 253 bytes, _ := proto.Marshal(esproto) 254 return crypto.Hash(bytes) 255 } 256 257 func TestSnapshotEmpty(t *testing.T) { 258 es := common.NewEquityShares(num.DecimalFromFloat(100)) 259 260 // Get the hash of an empty object 261 hash1 := getHash(es) 262 263 // Create a new object and load the snapshot into it 264 es2 := common.NewEquitySharesFromSnapshot(es.GetState()) 265 266 // Check the hash matches 267 hash2 := getHash(es2) 268 assert.Equal(t, hash1, hash2) 269 } 270 271 func TestSnapshotWithChanges(t *testing.T) { 272 es := common.NewEquityShares(num.DecimalFromFloat(100)) 273 274 // Get the hash of an empty object 275 hash1 := getHash(es) 276 277 // Make changes to the original object 278 for i := 0; i < 10; i++ { 279 id := fmt.Sprintf("ID%05d", i) 280 es.SetPartyStake(id, num.NewUint(uint64(i*100))) 281 } 282 283 // Check the hash has changed 284 hash2 := getHash(es) 285 assert.NotEqual(t, hash1, hash2) 286 287 // Restore the state into a new object 288 es2 := common.NewEquitySharesFromSnapshot(es.GetState()) 289 290 // Check the hashes match 291 hash3 := getHash(es2) 292 assert.Equal(t, hash2, hash3) 293 }