code.vegaprotocol.io/vega@v0.79.0/core/execution/future/equity_share_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 future_test 17 18 import ( 19 "context" 20 "testing" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 vegacontext "code.vegaprotocol.io/vega/libs/context" 26 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 27 "code.vegaprotocol.io/vega/libs/num" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 type equityShareMarket struct { 34 t *testing.T 35 tm *testMarket 36 parties map[string]struct{} 37 38 Now time.Time 39 ClosingAt time.Time 40 } 41 42 func newEquityShareMarket(t *testing.T) *equityShareMarket { 43 t.Helper() 44 now := time.Unix(10, 0) 45 closingAt := time.Unix(10000000000, 0) 46 47 return &equityShareMarket{ 48 t: t, 49 tm: getTestMarket(t, now, nil, &types.AuctionDuration{Duration: 1}), 50 parties: map[string]struct{}{}, 51 Now: now, 52 ClosingAt: closingAt, 53 } 54 } 55 56 func (esm *equityShareMarket) TestMarket() *testMarket { return esm.tm } 57 58 func (esm *equityShareMarket) BuildOrder(id, party string, side types.Side, price uint64) *types.Order { 59 return &types.Order{ 60 Type: types.OrderTypeLimit, 61 TimeInForce: types.OrderTimeInForceGTT, 62 Status: types.OrderStatusActive, 63 ID: id, 64 Side: side, 65 Party: party, 66 MarketID: esm.tm.market.GetID(), 67 Size: 1, 68 Price: num.NewUint(price), 69 Remaining: 1, 70 CreatedAt: esm.Now.UnixNano(), 71 ExpiresAt: esm.ClosingAt.UnixNano(), 72 } 73 } 74 75 func (esm *equityShareMarket) createPartyIfMissing(t *testing.T, party string) { 76 t.Helper() 77 if _, ok := esm.parties[party]; !ok { 78 esm.parties[party] = struct{}{} 79 addAccount(t, esm.tm, party) 80 } 81 } 82 83 func (esm *equityShareMarket) SubmitOrder(t *testing.T, ctx context.Context, order *types.Order) (*types.OrderConfirmation, error) { 84 t.Helper() 85 esm.createPartyIfMissing(t, order.Party) 86 return esm.tm.market.SubmitOrder(ctx, order) 87 } 88 89 func (esm *equityShareMarket) WithSubmittedOrder(t *testing.T, id, party string, side types.Side, price uint64) *equityShareMarket { 90 t.Helper() 91 ctx := context.Background() 92 order := esm.BuildOrder(id, party, side, price) 93 94 _, err := esm.SubmitOrder(t, ctx, order) 95 require.NoError(esm.t, err) 96 return esm 97 } 98 99 func (esm *equityShareMarket) WithSubmittedLiquidityProvision(t *testing.T, party, id string, amount uint64, fee string) *equityShareMarket { 100 t.Helper() 101 esm.createPartyIfMissing(t, party) 102 esm.tm.WithSubmittedLiquidityProvision(esm.t, party, amount, fee) 103 return esm 104 } 105 106 func (esm *equityShareMarket) LiquidityFeeAccount() *types.Account { 107 acc, err := esm.tm.collateralEngine.GetMarketLiquidityFeeAccount( 108 esm.tm.market.GetID(), esm.tm.asset, 109 ) 110 require.NoError(esm.t, err) 111 return acc 112 } 113 114 func (esm *equityShareMarket) PartyGeneralAccount(party string) *types.Account { 115 acc, err := esm.tm.collateralEngine.GetPartyGeneralAccount( 116 party, esm.tm.asset, 117 ) 118 require.NoError(esm.t, err) 119 return acc 120 } 121 122 func (esm *equityShareMarket) PartyLiquidityFeeAccount(party string) *types.Account { 123 acc, err := esm.tm.collateralEngine.GetPartyLiquidityFeeAccount( 124 esm.tm.market.GetID(), party, esm.tm.asset, 125 ) 126 require.NoError(esm.t, err) 127 return acc 128 } 129 130 func (esm *equityShareMarket) PartyMarginAccount(party string) *types.Account { 131 acc, err := esm.tm.collateralEngine.GetPartyMarginAccount( 132 esm.tm.market.GetID(), party, esm.tm.asset, 133 ) 134 require.NoError(esm.t, err) 135 return acc 136 } 137 138 func TestWithinMarket(t *testing.T) { 139 var ( 140 ctx = vegacontext.WithTraceID(context.Background(), vgcrypto.RandomHash()) 141 // as we will split fees in 1/3 and 2/3 142 // we use 900000 cause we need this number to be divisible by 3 143 matchingPrice = uint64(900000) 144 one = uint64(1) 145 ) 146 147 // Setup a market with a set of non-matching orders and Liquidity Provision 148 // Submissions from 2 parties. 149 esm := newEquityShareMarket(t). 150 WithSubmittedOrder(t, "some-id-1", "party1", types.SideSell, matchingPrice+one). 151 WithSubmittedOrder(t, "some-id-2", "party2", types.SideBuy, matchingPrice-one). 152 WithSubmittedOrder(t, "some-id-3", "party1", types.SideSell, matchingPrice). 153 WithSubmittedOrder(t, "some-id-4", "party2", types.SideBuy, matchingPrice). // Need to generate a trade to leave opening auction 154 // party1 (commitment: 2000) should get 2/3 of the fee 155 WithSubmittedLiquidityProvision(t, "party1", "lp-id-1", 2000000, "0.5"). 156 // party2 (commitment: 1000) should get 1/3 of the fee 157 WithSubmittedLiquidityProvision(t, "party2", "lp-id-2", 1000000, "0.5") 158 159 // tm is the testMarket instance 160 var ( 161 tm = esm.TestMarket() 162 curTime = esm.Now 163 ) 164 165 // End opening auction 166 curTime = curTime.Add(2 * time.Second) 167 tm.now = curTime 168 tm.market.OnTick(ctx, curTime) 169 170 md := esm.tm.market.GetMarketData() 171 require.NotNil(t, md) 172 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 173 174 t.Run("WhenNoTrades", func(t *testing.T) { 175 // clean up previous events 176 tm.events = []events.Event{} 177 178 // Trigger Fee distribution 179 curTime = curTime.Add(1 * time.Second) 180 tm.now = curTime 181 tm.market.OnTick(ctx, curTime) 182 183 // Assert the event 184 var evt *events.LedgerMovements 185 for _, e := range tm.events { 186 if e.Type() == events.LedgerMovementsEvent { 187 evt = e.(*events.LedgerMovements) 188 } 189 } 190 require.Nil(t, evt, "should receive no TransferEvent") 191 }) 192 193 // Match a pair of orders (same price) to trigger a fee distribution. 194 conf, err := esm. 195 WithSubmittedOrder(t, "some-id-3", "party1", types.SideSell, matchingPrice). 196 SubmitOrder(t, context.Background(), esm.BuildOrder("some-id-4", "party2", types.SideBuy, matchingPrice)) 197 require.NoError(t, err) 198 require.Len(t, conf.Trades, 1) 199 200 // Retrieve both MarketLiquidityFee account balance and Party Balance 201 // before the fee distribution. 202 203 originalBalance := esm.LiquidityFeeAccount().Balance.Clone() 204 205 curTime = curTime.Add(1 * time.Second) 206 tm.now = curTime 207 tm.market.OnTick(ctx, curTime) 208 209 md = esm.tm.market.GetMarketData() 210 require.NotNil(t, md) 211 require.Equal(t, types.MarketTradingModeContinuous, md.MarketTradingMode) 212 213 oneU := num.NewUint(1) 214 assert.True(t, esm.LiquidityFeeAccount().Balance.EQ(oneU), 215 "LiquidityFeeAccount should have a balance of 1 (remainder)") 216 217 // exp = originalBalance*(2/3) 218 exp := num.UintZero().Mul(num.Sum(oneU, oneU), originalBalance) 219 exp = exp.Div(exp, num.Sum(oneU, oneU, oneU)) 220 actual := esm.PartyLiquidityFeeAccount("party1").Balance 221 assert.True(t, 222 exp.EQ(actual), 223 "party1 should get 2/3 of the fees (got %s expected %s)", actual.String(), exp.String(), 224 ) 225 226 // exp = originalBalance*(1/3) 227 exp = num.UintZero().Div(originalBalance, num.Sum(oneU, oneU, oneU)) 228 // minus the remainder 229 exp.Sub(exp, oneU) 230 actual = esm.PartyLiquidityFeeAccount("party2").Balance 231 assert.True(t, 232 exp.EQ(actual), 233 "party2 should get 2/3 of the fees (got %s expected %s)", actual.String(), exp.String(), 234 ) 235 }