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  }