code.vegaprotocol.io/vega@v0.79.0/core/execution/common/market_activity_tracker_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 "bytes" 20 "context" 21 "testing" 22 "time" 23 24 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 25 "code.vegaprotocol.io/vega/core/execution/common" 26 "code.vegaprotocol.io/vega/core/execution/common/mocks" 27 "code.vegaprotocol.io/vega/core/integration/stubs" 28 snp "code.vegaprotocol.io/vega/core/snapshot" 29 "code.vegaprotocol.io/vega/core/stats" 30 "code.vegaprotocol.io/vega/core/types" 31 "code.vegaprotocol.io/vega/libs/num" 32 "code.vegaprotocol.io/vega/libs/proto" 33 vgrand "code.vegaprotocol.io/vega/libs/rand" 34 vgtest "code.vegaprotocol.io/vega/libs/test" 35 "code.vegaprotocol.io/vega/logging" 36 "code.vegaprotocol.io/vega/paths" 37 vgproto "code.vegaprotocol.io/vega/protos/vega" 38 snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 39 40 "github.com/golang/mock/gomock" 41 "github.com/stretchr/testify/assert" 42 "github.com/stretchr/testify/require" 43 ) 44 45 type TestEpochEngine struct { 46 target func(context.Context, types.Epoch) 47 } 48 49 func (e *TestEpochEngine) NotifyOnEpoch(f func(context.Context, types.Epoch), _ func(context.Context, types.Epoch)) { 50 e.target = f 51 } 52 53 type EligibilityChecker struct{} 54 55 func (e *EligibilityChecker) IsEligibleForProposerBonus(marketID string, volumeTraded *num.Uint) bool { 56 return volumeTraded.GT(num.NewUint(5000)) 57 } 58 59 func TestMarketTracker(t *testing.T) { 60 ctrl := gomock.NewController(t) 61 teams := mocks.NewMockTeams(ctrl) 62 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 63 64 broker := bmocks.NewMockBroker(ctrl) 65 collateralService := mocks.NewMockCollateral(ctrl) 66 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 67 tracker.SetEligibilityChecker(&EligibilityChecker{}) 68 69 tracker.MarketProposed("asset1", "market1", "me") 70 tracker.MarketProposed("asset1", "market2", "me2") 71 72 assert.True(t, tracker.MarketTrackedForAsset("market1", "asset1")) 73 assert.False(t, tracker.MarketTrackedForAsset("market1", "asset2")) 74 75 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 76 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 77 78 tracker.AddValueTraded("asset1", "market1", num.NewUint(1000)) 79 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 80 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 81 82 tracker.AddValueTraded("asset1", "market2", num.NewUint(4000)) 83 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 84 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 85 86 tracker.AddValueTraded("asset1", "market2", num.NewUint(1001)) 87 tracker.AddValueTraded("asset1", "market1", num.NewUint(4001)) 88 89 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 90 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 91 92 // mark as paid 93 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{}, "zohar") 94 tracker.MarkPaidProposer("asset1", "market2", "VEGA", []string{}, "zohar") 95 96 // check if eligible for the same combo, expect false 97 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 98 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 99 100 // now check for another funder 101 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "jeremy")) 102 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "jeremy")) 103 104 // mark as paid 105 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{}, "jeremy") 106 tracker.MarkPaidProposer("asset1", "market2", "VEGA", []string{}, "jeremy") 107 108 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "jeremy")) 109 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "jeremy")) 110 111 // check for another payout asset 112 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{}, "zohar")) 113 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{}, "zohar")) 114 115 tracker.MarkPaidProposer("asset1", "market1", "USDC", []string{}, "zohar") 116 tracker.MarkPaidProposer("asset1", "market2", "USDC", []string{}, "zohar") 117 118 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{}, "zohar")) 119 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{}, "zohar")) 120 121 // check for another market scope 122 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{"market1"}, "zohar")) 123 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{"market2"}, "zohar")) 124 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{"market1", "market2"}, "zohar")) 125 require.Equal(t, true, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{"market2", "market2"}, "zohar")) 126 127 tracker.MarkPaidProposer("asset1", "market1", "USDC", []string{"market1"}, "zohar") 128 tracker.MarkPaidProposer("asset1", "market2", "USDC", []string{"market2"}, "zohar") 129 tracker.MarkPaidProposer("asset1", "market1", "USDC", []string{"market1", "market2"}, "zohar") 130 tracker.MarkPaidProposer("asset1", "market2", "USDC", []string{"market1", "market2"}, "zohar") 131 132 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{"market1"}, "zohar")) 133 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{"market2"}, "zohar")) 134 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{"market1", "market2"}, "zohar")) 135 require.Equal(t, false, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{"market1", "market2"}, "zohar")) 136 137 // take a snapshot 138 key := (&types.PayloadMarketActivityTracker{}).Key() 139 state1, _, err := tracker.GetState(key) 140 require.NoError(t, err) 141 teams2 := mocks.NewMockTeams(ctrl) 142 balanceChecker2 := mocks.NewMockAccountBalanceChecker(ctrl) 143 broker = bmocks.NewMockBroker(ctrl) 144 trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams2, balanceChecker2, broker, collateralService) 145 pl := snapshotpb.Payload{} 146 require.NoError(t, proto.Unmarshal(state1, &pl)) 147 148 additionalProvider, err := trackerLoad.LoadState(context.Background(), types.PayloadFromProto(&pl)) 149 require.NoError(t, err) 150 assert.Nil(t, additionalProvider) 151 152 state2, _, err := trackerLoad.GetState(key) 153 require.NoError(t, err) 154 require.True(t, bytes.Equal(state1, state2)) 155 } 156 157 func TestRemoveMarket(t *testing.T) { 158 epochService := &TestEpochEngine{} 159 ctrl := gomock.NewController(t) 160 teams := mocks.NewMockTeams(ctrl) 161 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 162 163 broker := bmocks.NewMockBroker(ctrl) 164 collateralService := mocks.NewMockCollateral(ctrl) 165 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 166 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 167 tracker.SetEligibilityChecker(&EligibilityChecker{}) 168 tracker.MarketProposed("asset1", "market1", "me") 169 tracker.MarketProposed("asset1", "market2", "me2") 170 require.Equal(t, 2, len(tracker.GetAllMarketIDs())) 171 require.Equal(t, "market1", tracker.GetAllMarketIDs()[0]) 172 require.Equal(t, "market2", tracker.GetAllMarketIDs()[1]) 173 174 // remove the market - this should only mark the market for removal 175 tracker.RemoveMarket("asset1", "market1") 176 require.Equal(t, 2, len(tracker.GetAllMarketIDs())) 177 require.Equal(t, "market1", tracker.GetAllMarketIDs()[0]) 178 require.Equal(t, "market2", tracker.GetAllMarketIDs()[1]) 179 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START}) 180 181 require.Equal(t, 1, len(tracker.GetAllMarketIDs())) 182 require.Equal(t, "market2", tracker.GetAllMarketIDs()[0]) 183 } 184 185 func TestAddRemoveAMM(t *testing.T) { 186 epochService := &TestEpochEngine{} 187 ctrl := gomock.NewController(t) 188 teams := mocks.NewMockTeams(ctrl) 189 broker := bmocks.NewMockBroker(ctrl) 190 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 191 192 collateralService := mocks.NewMockCollateral(ctrl) 193 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 194 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 195 tracker.SetEligibilityChecker(&EligibilityChecker{}) 196 tracker.MarketProposed("asset1", "market1", "me") 197 tracker.MarketProposed("asset1", "market2", "me2") 198 require.Equal(t, 2, len(tracker.GetAllMarketIDs())) 199 require.Equal(t, "market1", tracker.GetAllMarketIDs()[0]) 200 require.Equal(t, "market2", tracker.GetAllMarketIDs()[1]) 201 202 tracker.AddAMMSubAccount("asset1", "market1", "sub1") 203 tracker.AddAMMSubAccount("asset1", "market1", "sub2") 204 205 require.Equal(t, map[string]struct{}{"sub1": {}, "sub2": {}}, tracker.GetAllAMMParties("asset1", nil)) 206 207 tracker.RemoveAMMParty("asset1", "market1", "sub2") 208 require.Equal(t, map[string]struct{}{"sub1": {}}, tracker.GetAllAMMParties("asset1", nil)) 209 210 tracker.RemoveAMMParty("asset1", "market1", "sub1") 211 require.Equal(t, map[string]struct{}{}, tracker.GetAllAMMParties("asset1", nil)) 212 } 213 214 func TestCalculateTotalMakerContributionInQuantum(t *testing.T) { 215 // ctx := context.Background() 216 epochService := &TestEpochEngine{} 217 ctrl := gomock.NewController(t) 218 teams := mocks.NewMockTeams(ctrl) 219 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 220 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 221 broker := bmocks.NewMockBroker(ctrl) 222 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 223 broker.EXPECT().Send(gomock.Any()).AnyTimes() 224 collateralService := mocks.NewMockCollateral(ctrl) 225 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 226 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 227 tracker.SetEligibilityChecker(&EligibilityChecker{}) 228 tracker.MarketProposed("asset1", "market1", "me") 229 tracker.MarketProposed("asset1", "market2", "me2") 230 tracker.MarketProposed("asset1", "market4", "me4") 231 tracker.MarketProposed("asset2", "market3", "me3") 232 233 collateralService.EXPECT().GetAssetQuantum("asset1").Return(num.DecimalOne(), nil).AnyTimes() 234 collateralService.EXPECT().GetAssetQuantum("asset2").Return(num.DecimalTwo(), nil).AnyTimes() 235 // no fees generated expect empty slice 236 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 237 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 238 239 // expect no makers fee contributors 240 contribution, fraction := tracker.CalculateTotalMakerContributionInQuantum(1) 241 require.Equal(t, 0, len(fraction)) 242 require.Equal(t, 0, len(contribution)) 243 244 contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(2) 245 require.Equal(t, 0, len(fraction)) 246 require.Equal(t, 0, len(contribution)) 247 248 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 249 250 // now lets get some fees paid 251 // update with a few transfers 252 transfersM1 := []*types.Transfer{ 253 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 254 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 255 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 256 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 257 } 258 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 259 260 transfersM2 := []*types.Transfer{ 261 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(500)}}, 262 } 263 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 264 265 transfersM3 := []*types.Transfer{ 266 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(450)}}, 267 } 268 tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM3) 269 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 270 271 contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(1) 272 // party 1 received in total: 273 // asset1 : 1000 274 // party 2 received in total: 275 // asset1 : 1500 276 // asset2: 450/2 = 225 277 require.Equal(t, 2, len(fraction)) 278 require.Equal(t, 2, len(contribution)) 279 require.Equal(t, "1000", contribution["party1"].String()) 280 require.Equal(t, "1725", contribution["party2"].String()) 281 require.Equal(t, "0.3669724770642202", fraction["party1"].String()) 282 require.Equal(t, "0.6330275229357798", fraction["party2"].String()) 283 284 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 285 286 transfersM4 := []*types.Transfer{ 287 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(2000)}}, 288 } 289 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM4) 290 291 transfersM5 := []*types.Transfer{ 292 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(550)}}, 293 } 294 tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM5) 295 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 296 297 // party 1 received in total this epoch: 298 // asset1 : 2000 299 // party 2 received in total: 300 // asset2: 550/2 = 275 301 // in total for both epochs: 302 // party1 = 3000 303 // party2 = 2000 304 contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(2) 305 require.Equal(t, 2, len(fraction)) 306 require.Equal(t, 2, len(contribution)) 307 require.Equal(t, "3000", contribution["party1"].String()) 308 require.Equal(t, "2000", contribution["party2"].String()) 309 require.Equal(t, "0.6", fraction["party1"].String()) 310 require.Equal(t, "0.4", fraction["party2"].String()) 311 } 312 313 func TestGetScores(t *testing.T) { 314 ctx := context.Background() 315 epochService := &TestEpochEngine{} 316 ctrl := gomock.NewController(t) 317 teams := mocks.NewMockTeams(ctrl) 318 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 319 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 320 broker := bmocks.NewMockBroker(ctrl) 321 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 322 broker.EXPECT().Send(gomock.Any()).AnyTimes() 323 collateralService := mocks.NewMockCollateral(ctrl) 324 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 325 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 326 tracker.SetEligibilityChecker(&EligibilityChecker{}) 327 tracker.MarketProposed("asset1", "market1", "me") 328 tracker.MarketProposed("asset1", "market2", "me2") 329 tracker.MarketProposed("asset1", "market4", "me4") 330 tracker.MarketProposed("asset2", "market3", "me3") 331 332 // no fees generated expect empty slice 333 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 334 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 335 336 // asset1, asset2 no market scoping 337 338 for _, asset := range []string{"asset1", "asset2"} { 339 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 340 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: asset, Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 341 require.Equal(t, 0, len(scores)) 342 } 343 } 344 345 // asset1 one market in scope 346 for _, market := range []string{"market1", "market2", "market4"} { 347 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 348 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{market}}) 349 require.Equal(t, 0, len(scores)) 350 } 351 } 352 353 // asset2 one market in scope 354 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 355 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market3"}}) 356 require.Equal(t, 0, len(scores)) 357 } 358 359 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 360 361 // update with a few transfers 362 transfersM1 := []*types.Transfer{ 363 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 364 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 365 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 366 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 367 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 368 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 369 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 370 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 371 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 372 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 373 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 374 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 375 } 376 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 377 378 transfersM2 := []*types.Transfer{ 379 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(500)}}, 380 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 381 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 382 } 383 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 384 385 transfersM3 := []*types.Transfer{ 386 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(500)}}, 387 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(450)}}, 388 } 389 tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM3) 390 391 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 392 393 // looking across all markets in asset 1 with window length 1: 394 // party1: 800 395 // partt2: 3200 396 // total = 4000 397 // party1 = 800/4000 = 0.2 398 // party2 = 3200/4000 = 0.8 399 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 400 require.Equal(t, 2, len(scores)) 401 402 require.Equal(t, "party1", scores[0].Party) 403 require.Equal(t, "0.2", scores[0].Score.String()) 404 require.Equal(t, "party2", scores[1].Party) 405 require.Equal(t, "0.8", scores[1].Score.String()) 406 407 // now look only on market 1: 408 // party1 = 800/2500 = 0.32 409 // partt2 = 1700/2500 = 0.68 410 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 411 require.Equal(t, 2, len(scores)) 412 413 require.Equal(t, "party1", scores[0].Party) 414 require.Equal(t, "0.32", scores[0].Score.String()) 415 require.Equal(t, "party2", scores[1].Party) 416 require.Equal(t, "0.68", scores[1].Score.String()) 417 418 // now look only on market 2: 419 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 420 require.Equal(t, 2, len(scores)) 421 require.Equal(t, "party2", scores[1].Party) 422 require.Equal(t, "1", scores[1].Score.String()) 423 require.Equal(t, true, scores[1].IsEligible) 424 require.Equal(t, "party1", scores[0].Party) 425 require.Equal(t, false, scores[0].IsEligible) 426 require.Equal(t, "0", scores[0].Score.String()) 427 428 // now look at asset2 with no market qualifer 429 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset2", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 430 require.Equal(t, 2, len(scores)) 431 require.Equal(t, "party1", scores[0].Party) 432 require.Equal(t, "1", scores[0].Score.String()) 433 require.Equal(t, true, scores[0].IsEligible) 434 require.Equal(t, "party2", scores[1].Party) 435 require.Equal(t, "0", scores[1].Score.String()) 436 require.Equal(t, false, scores[1].IsEligible) 437 438 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 439 transfersM1 = []*types.Transfer{ 440 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1200)}}, 441 } 442 transfersM2 = []*types.Transfer{ 443 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 444 } 445 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 446 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 447 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 448 449 // looking across all markets in asset 1 with window length 2: 450 // party1: 800 + 1200 = 2000 451 // partt2: 3200 + 800 = 4000 452 // total = 4000 + 2000 = 6000 453 // party1 = 2000/6000 = 1/3 454 // party2 = 4000/6000 = 2/3 455 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 456 require.Equal(t, 2, len(scores)) 457 458 require.Equal(t, "party1", scores[0].Party) 459 require.Equal(t, "0.3333333333333333", scores[0].Score.String()) 460 require.Equal(t, "party2", scores[1].Party) 461 require.Equal(t, "0.6666666666666667", scores[1].Score.String()) 462 } 463 464 func TestGetScoresIndividualsDifferentScopes(t *testing.T) { 465 ctx := context.Background() 466 epochService := &TestEpochEngine{} 467 ctrl := gomock.NewController(t) 468 teams := mocks.NewMockTeams(ctrl) 469 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 470 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 471 broker := bmocks.NewMockBroker(ctrl) 472 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 473 broker.EXPECT().Send(gomock.Any()).AnyTimes() 474 collateralService := mocks.NewMockCollateral(ctrl) 475 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 476 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 477 tracker.SetEligibilityChecker(&EligibilityChecker{}) 478 tracker.MarketProposed("asset1", "market1", "me") 479 tracker.MarketProposed("asset1", "market2", "me2") 480 tracker.MarketProposed("asset1", "market4", "me4") 481 tracker.MarketProposed("asset2", "market3", "me3") 482 483 // no fees generated expect empty slice 484 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 485 epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 486 487 // asset1, asset2 no market scoping 488 489 for _, asset := range []string{"asset1", "asset2"} { 490 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 491 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: asset, Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 492 require.Equal(t, 0, len(scores)) 493 } 494 } 495 496 // asset1 one market in scope 497 for _, market := range []string{"market1", "market2", "market4"} { 498 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 499 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{market}}) 500 require.Equal(t, 0, len(scores)) 501 } 502 } 503 504 // asset2 one market in scope 505 for _, m := range []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} { 506 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market3"}}) 507 require.Equal(t, 0, len(scores)) 508 } 509 510 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 511 512 tracker.AddAMMSubAccount("asset1", "market1", "party1") 513 514 // update with a few transfers 515 transfersM1 := []*types.Transfer{ 516 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 517 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 518 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 519 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 520 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 521 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 522 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 523 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 524 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 525 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 526 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 527 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 528 } 529 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 530 531 transfersM2 := []*types.Transfer{ 532 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(500)}}, 533 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 534 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 535 } 536 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 537 538 transfersM3 := []*types.Transfer{ 539 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(500)}}, 540 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(450)}}, 541 } 542 tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM3) 543 544 epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 545 546 // looking across all markets in asset 1 with window length 1: 547 // party1: 800 548 // partt2: 3200 549 // total = 4000 550 // party1 = 800/4000 = 0.2 551 // party2 = 3200/4000 = 0.8 552 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 553 require.Equal(t, 2, len(scores)) 554 555 require.Equal(t, "party1", scores[0].Party) 556 require.Equal(t, "0.2", scores[0].Score.String()) 557 require.Equal(t, "party2", scores[1].Party) 558 require.Equal(t, "0.8", scores[1].Score.String()) 559 560 // looking across all markets in asset 1 with window length 1 and AMM scope: 561 // party1: 800 562 // partt2: 3200 563 // total = 4000 564 // party1 = 800/4000 = 0.2 565 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_AMM, WindowLength: 1}) 566 require.Equal(t, 1, len(scores)) 567 568 require.Equal(t, "party1", scores[0].Party) 569 require.Equal(t, "0.2", scores[0].Score.String()) 570 571 // now look only on market 1: 572 // party1 = 800/2500 = 0.32 573 // partt2 = 1700/2500 = 0.68 574 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 575 require.Equal(t, 2, len(scores)) 576 577 require.Equal(t, "party1", scores[0].Party) 578 require.Equal(t, "0.32", scores[0].Score.String()) 579 require.Equal(t, "party2", scores[1].Party) 580 require.Equal(t, "0.68", scores[1].Score.String()) 581 582 // now look only on market 2: 583 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 584 require.Equal(t, 2, len(scores)) 585 586 require.Equal(t, "party1", scores[0].Party) 587 require.Equal(t, "0", scores[0].Score.String()) 588 require.Equal(t, false, scores[0].IsEligible) 589 require.Equal(t, "party2", scores[1].Party) 590 require.Equal(t, "1", scores[1].Score.String()) 591 require.Equal(t, true, scores[1].IsEligible) 592 593 // now look at asset2 with no market qualifer 594 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset2", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 595 require.Equal(t, 2, len(scores)) 596 require.Equal(t, "party1", scores[0].Party) 597 require.Equal(t, true, scores[0].IsEligible) 598 require.Equal(t, "1", scores[0].Score.String()) 599 require.Equal(t, "party2", scores[1].Party) 600 require.Equal(t, "0", scores[1].Score.String()) 601 require.Equal(t, false, scores[1].IsEligible) 602 603 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 604 transfersM1 = []*types.Transfer{ 605 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1200)}}, 606 } 607 transfersM2 = []*types.Transfer{ 608 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 609 } 610 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 611 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 612 epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 613 614 // looking across all markets in asset 1 with window length 2: 615 // party1: 800 + 1200 = 2000 616 // partt2: 3200 + 800 = 4000 617 // total = 4000 + 2000 = 6000 618 // party1 = 2000/6000 = 1/3 619 // party2 = 4000/6000 = 2/3 620 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 621 require.Equal(t, 2, len(scores)) 622 623 require.Equal(t, "party1", scores[0].Party) 624 require.Equal(t, "0.3333333333333333", scores[0].Score.String()) 625 require.Equal(t, "party2", scores[1].Party) 626 require.Equal(t, "0.6666666666666667", scores[1].Score.String()) 627 } 628 629 func TestMarketTrackerStateChange(t *testing.T) { 630 key := (&types.PayloadMarketActivityTracker{}).Key() 631 632 ctrl := gomock.NewController(t) 633 teams := mocks.NewMockTeams(ctrl) 634 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 635 broker := bmocks.NewMockBroker(ctrl) 636 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 637 collateralService := mocks.NewMockCollateral(ctrl) 638 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 639 tracker.SetEligibilityChecker(&EligibilityChecker{}) 640 641 state1, _, err := tracker.GetState(key) 642 require.NoError(t, err) 643 644 tracker.MarketProposed("asset1", "market1", "me") 645 tracker.MarketProposed("asset1", "market2", "me2") 646 647 state2, _, err := tracker.GetState(key) 648 require.NoError(t, err) 649 require.False(t, bytes.Equal(state1, state2)) 650 651 tracker.AddValueTraded("asset1", "market1", num.NewUint(1000)) 652 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 653 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 654 655 state3, _, err := tracker.GetState(key) 656 require.NoError(t, err) 657 require.False(t, bytes.Equal(state1, state3)) 658 } 659 660 func TestFeesTrackerWith0(t *testing.T) { 661 epochEngine := &TestEpochEngine{} 662 ctx := context.Background() 663 ctrl := gomock.NewController(t) 664 teams := mocks.NewMockTeams(ctrl) 665 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 666 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 667 broker := bmocks.NewMockBroker(ctrl) 668 broker.EXPECT().Send(gomock.Any()).AnyTimes() 669 collateralService := mocks.NewMockCollateral(ctrl) 670 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 671 epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 672 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 673 674 tracker.MarketProposed("asset1", "market1", "me") 675 transfersM1 := []*types.Transfer{ 676 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 677 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 678 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 679 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 680 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 681 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 682 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 683 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 684 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 685 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 686 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 687 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.UintZero()}}, 688 } 689 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 690 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 691 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 692 require.Equal(t, 2, len(scores)) 693 require.Equal(t, false, scores[0].IsEligible) 694 require.Equal(t, false, scores[1].IsEligible) 695 } 696 697 func TestGetLastEpochTakeFees(t *testing.T) { 698 epochEngine := &TestEpochEngine{} 699 ctx := context.Background() 700 ctrl := gomock.NewController(t) 701 teams := mocks.NewMockTeams(ctrl) 702 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 703 broker := bmocks.NewMockBroker(ctrl) 704 collateralService := mocks.NewMockCollateral(ctrl) 705 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 706 epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 707 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 708 tracker.SetEligibilityChecker(&EligibilityChecker{}) 709 710 partyScores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 711 require.Equal(t, 0, len(partyScores)) 712 713 tracker.MarketProposed("asset1", "market1", "me") 714 tracker.MarketProposed("asset1", "market2", "me2") 715 716 // update with a few transfers 717 transfersM1 := []*types.Transfer{ 718 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 719 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 720 {Owner: "party1", Type: types.TransferTypeInfrastructureFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(110)}}, 721 {Owner: "party1", Type: types.TransferTypeLiquidityFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(10)}}, 722 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 723 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 724 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 725 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 726 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 727 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 728 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 729 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 730 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 731 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 732 } 733 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 734 735 transfersM2 := []*types.Transfer{ 736 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 737 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 738 } 739 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 740 741 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 742 743 m1 := tracker.GetLastEpochTakeFees("asset1", []string{"market1"}, 1) 744 require.Equal(t, 2, len(m1)) 745 require.Equal(t, "620", m1["party1"].String()) 746 require.Equal(t, "1000", m1["party2"].String()) 747 m2 := tracker.GetLastEpochTakeFees("asset1", []string{"market2"}, 1) 748 require.Equal(t, 1, len(m2)) 749 require.Equal(t, "150", m2["party2"].String()) 750 751 mAll := tracker.GetLastEpochTakeFees("asset1", []string{"market1", "market2"}, 1) 752 require.Equal(t, "620", mAll["party1"].String()) 753 require.Equal(t, "1150", mAll["party2"].String()) 754 755 mNoMarkets := tracker.GetLastEpochTakeFees("asset1", []string{}, 1) 756 require.Equal(t, "620", mNoMarkets["party1"].String()) 757 require.Equal(t, "1150", mNoMarkets["party2"].String()) 758 759 require.Equal(t, mAll, mNoMarkets) 760 } 761 762 func TestGetLastEpochTakeFeesMultiEpochWindow(t *testing.T) { 763 epochEngine := &TestEpochEngine{} 764 ctx := context.Background() 765 ctrl := gomock.NewController(t) 766 teams := mocks.NewMockTeams(ctrl) 767 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 768 broker := bmocks.NewMockBroker(ctrl) 769 collateralService := mocks.NewMockCollateral(ctrl) 770 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 771 epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 772 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 773 tracker.SetEligibilityChecker(&EligibilityChecker{}) 774 775 partyScores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 776 require.Equal(t, 0, len(partyScores)) 777 778 tracker.MarketProposed("asset1", "market1", "me") 779 tracker.MarketProposed("asset1", "market2", "me2") 780 781 // update with a few transfers 782 transfersM1 := []*types.Transfer{ 783 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 784 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 785 {Owner: "party1", Type: types.TransferTypeInfrastructureFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(110)}}, 786 {Owner: "party1", Type: types.TransferTypeLiquidityFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(10)}}, 787 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 788 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 789 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 790 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 791 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 792 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 793 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 794 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 795 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 796 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 797 } 798 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 799 800 transfersM2 := []*types.Transfer{ 801 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 802 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 803 } 804 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 805 806 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 807 epochEngine.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 808 809 // double the fees for a window of 2 810 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 811 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 812 813 epochEngine.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 814 m1 := tracker.GetLastEpochTakeFees("asset1", []string{"market1"}, 1) 815 require.Equal(t, 2, len(m1)) 816 require.Equal(t, "620", m1["party1"].String()) 817 require.Equal(t, "1000", m1["party2"].String()) 818 819 m1 = tracker.GetLastEpochTakeFees("asset1", []string{"market1"}, 2) 820 require.Equal(t, 2, len(m1)) 821 require.Equal(t, "1240", m1["party1"].String()) 822 require.Equal(t, "2000", m1["party2"].String()) 823 824 m2 := tracker.GetLastEpochTakeFees("asset1", []string{"market2"}, 1) 825 require.Equal(t, 1, len(m2)) 826 require.Equal(t, "150", m2["party2"].String()) 827 828 m2 = tracker.GetLastEpochTakeFees("asset1", []string{"market2"}, 2) 829 require.Equal(t, 1, len(m2)) 830 require.Equal(t, "300", m2["party2"].String()) 831 832 mAll := tracker.GetLastEpochTakeFees("asset1", []string{"market1", "market2"}, 1) 833 require.Equal(t, "620", mAll["party1"].String()) 834 require.Equal(t, "1150", mAll["party2"].String()) 835 836 mAll = tracker.GetLastEpochTakeFees("asset1", []string{"market1", "market2"}, 2) 837 require.Equal(t, "1240", mAll["party1"].String()) 838 require.Equal(t, "2300", mAll["party2"].String()) 839 840 mNoMarkets := tracker.GetLastEpochTakeFees("asset1", []string{}, 1) 841 require.Equal(t, "620", mNoMarkets["party1"].String()) 842 require.Equal(t, "1150", mNoMarkets["party2"].String()) 843 844 mNoMarkets = tracker.GetLastEpochTakeFees("asset1", []string{}, 2) 845 require.Equal(t, "1240", mNoMarkets["party1"].String()) 846 require.Equal(t, "2300", mNoMarkets["party2"].String()) 847 require.Equal(t, mAll, mNoMarkets) 848 } 849 850 func TestFeesTracker(t *testing.T) { 851 epochEngine := &TestEpochEngine{} 852 ctx := context.Background() 853 ctrl := gomock.NewController(t) 854 teams := mocks.NewMockTeams(ctrl) 855 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 856 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 857 broker := bmocks.NewMockBroker(ctrl) 858 broker.EXPECT().Send(gomock.Any()).AnyTimes() 859 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 860 collateralService := mocks.NewMockCollateral(ctrl) 861 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 862 epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 863 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 864 tracker.SetEligibilityChecker(&EligibilityChecker{}) 865 866 partyScores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 867 require.Equal(t, 0, len(partyScores)) 868 869 key := (&types.PayloadMarketActivityTracker{}).Key() 870 state1, _, err := tracker.GetState(key) 871 require.NoError(t, err) 872 873 tracker.MarketProposed("asset1", "market1", "me") 874 tracker.MarketProposed("asset1", "market2", "me2") 875 876 // update with a few transfers 877 transfersM1 := []*types.Transfer{ 878 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 879 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 880 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 881 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 882 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 883 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 884 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 885 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 886 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 887 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 888 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 889 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 890 } 891 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 892 893 transfersM2 := []*types.Transfer{ 894 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 895 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(150)}}, 896 } 897 tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) 898 899 epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 900 epochEngine.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) 901 902 // asset1, types.TransferTypeMakerFeeReceive 903 // party1 received 500 904 // party2 received 1500 905 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 906 require.Equal(t, 2, len(scores)) 907 require.Equal(t, "0.25", scores[0].Score.String()) 908 require.Equal(t, "party1", scores[0].Party) 909 require.Equal(t, "0.75", scores[1].Score.String()) 910 require.Equal(t, "party2", scores[1].Party) 911 912 // asset1 TransferTypeMakerFeePay 913 // party1 paid 500 914 // party2 paid 1000 915 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 916 require.Equal(t, 2, len(scores)) 917 require.Equal(t, "0.3333333333333333", scores[0].Score.String()) 918 require.Equal(t, "party1", scores[0].Party) 919 require.Equal(t, "0.6666666666666667", scores[1].Score.String()) 920 require.Equal(t, "party2", scores[1].Party) 921 922 // asset1 TransferTypeLiquidityFeeNetDistribute 923 // party1 paid 800 924 // party2 paid 1700 925 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 926 require.Equal(t, 2, len(scores)) 927 require.Equal(t, "0.32", scores[0].Score.String()) 928 require.Equal(t, "party1", scores[0].Party) 929 require.Equal(t, "0.68", scores[1].Score.String()) 930 require.Equal(t, "party2", scores[1].Party) 931 932 // asset2 TransferTypeMakerFeePay 933 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 934 require.Equal(t, 2, len(scores)) 935 require.Equal(t, "1", scores[0].Score.String()) 936 require.Equal(t, "party1", scores[0].Party) 937 require.Equal(t, true, scores[0].IsEligible) 938 require.Equal(t, "0", scores[1].Score.String()) 939 require.Equal(t, "party2", scores[1].Party) 940 require.Equal(t, false, scores[1].IsEligible) 941 942 // asset2 TransferTypeMakerFeePay 943 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 944 require.Equal(t, 2, len(scores)) 945 require.Equal(t, "0", scores[0].Score.String()) 946 require.Equal(t, "party1", scores[0].Party) 947 require.Equal(t, false, scores[0].IsEligible) 948 require.Equal(t, "1", scores[1].Score.String()) 949 require.Equal(t, "party2", scores[1].Party) 950 require.Equal(t, true, scores[1].IsEligible) 951 952 // check state has changed 953 state2, _, err := tracker.GetState(key) 954 require.NoError(t, err) 955 require.False(t, bytes.Equal(state1, state2)) 956 957 epochEngineLoad := &TestEpochEngine{} 958 ctrl = gomock.NewController(t) 959 teams = mocks.NewMockTeams(ctrl) 960 balanceChecker = mocks.NewMockAccountBalanceChecker(ctrl) 961 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 962 broker.EXPECT().Send(gomock.Any()).AnyTimes() 963 trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 964 epochEngineLoad.NotifyOnEpoch(trackerLoad.OnEpochEvent, trackerLoad.OnEpochRestore) 965 966 pl := snapshotpb.Payload{} 967 require.NoError(t, proto.Unmarshal(state2, &pl)) 968 969 additionalProvider, err := trackerLoad.LoadState(context.Background(), types.PayloadFromProto(&pl)) 970 require.NoError(t, err) 971 assert.Nil(t, additionalProvider) 972 973 state3, _, err := trackerLoad.GetState(key) 974 require.NoError(t, err) 975 require.True(t, bytes.Equal(state2, state3)) 976 977 // check a restored party exist in the restored engine 978 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 979 require.Equal(t, 2, len(scores)) 980 require.Equal(t, "1", scores[0].Score.String()) 981 require.Equal(t, "party1", scores[0].Party) 982 require.Equal(t, true, scores[0].IsEligible) 983 require.Equal(t, "0", scores[1].Score.String()) 984 require.Equal(t, "party2", scores[1].Party) 985 require.Equal(t, false, scores[1].IsEligible) 986 987 // end the epoch 988 epochEngineLoad.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) 989 990 // // NewEngine epoch should scrub the state an produce a difference hash 991 state4, _, err := trackerLoad.GetState(key) 992 require.NoError(t, err) 993 require.False(t, bytes.Equal(state3, state4)) 994 995 // // new epoch, we expect the metrics to have been reset 996 997 metrics := []vgproto.DispatchMetric{vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID, vgproto.DispatchMetric_DISPATCH_METRIC_LP_FEES_RECEIVED, vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED} 998 for _, m := range metrics { 999 scores = trackerLoad.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market1"}}) 1000 require.Equal(t, 2, len(scores)) 1001 require.Equal(t, "0", scores[0].Score.String()) 1002 require.Equal(t, "party1", scores[0].Party) 1003 require.Equal(t, false, scores[0].IsEligible) 1004 require.Equal(t, "0", scores[1].Score.String()) 1005 require.Equal(t, "party2", scores[1].Party) 1006 require.Equal(t, false, scores[1].IsEligible) 1007 scores = trackerLoad.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "asset1", Metric: m, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1, Markets: []string{"market2"}}) 1008 require.Equal(t, 2, len(scores)) 1009 require.Equal(t, "0", scores[0].Score.String()) 1010 require.Equal(t, "party1", scores[0].Party) 1011 require.Equal(t, false, scores[0].IsEligible) 1012 require.Equal(t, "0", scores[1].Score.String()) 1013 require.Equal(t, "party2", scores[1].Party) 1014 require.Equal(t, false, scores[1].IsEligible) 1015 } 1016 } 1017 1018 func TestDecimalSerialisation(t *testing.T) { 1019 d := num.DecimalE() 1020 b, err := d.MarshalBinary() 1021 require.NoError(t, err) 1022 dd, err := num.UnmarshalBinaryDecimal(b) 1023 require.NoError(t, err) 1024 require.Equal(t, d, dd) 1025 } 1026 1027 func TestUintSerialisation(t *testing.T) { 1028 ui, _ := num.UintFromString("1000000000000000000", 10) 1029 b := ui.Bytes() 1030 bb := b[:] 1031 uiLoad := num.UintFromBytes(bb) 1032 require.Equal(t, ui, uiLoad) 1033 } 1034 1035 func TestSnapshot(t *testing.T) { 1036 tracker := setupDefaultTrackerForTest(t) 1037 1038 // take a snapshot 1039 key := (&types.PayloadMarketActivityTracker{}).Key() 1040 state1, _, err := tracker.GetState(key) 1041 require.NoError(t, err) 1042 1043 ctrl := gomock.NewController(t) 1044 teams := mocks.NewMockTeams(ctrl) 1045 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1046 broker := bmocks.NewMockBroker(ctrl) 1047 collateralService := mocks.NewMockCollateral(ctrl) 1048 trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1049 pl := snapshotpb.Payload{} 1050 require.NoError(t, proto.Unmarshal(state1, &pl)) 1051 1052 additionalProvider, err := trackerLoad.LoadState(context.Background(), types.PayloadFromProto(&pl)) 1053 require.NoError(t, err) 1054 assert.Nil(t, additionalProvider) 1055 1056 state2, _, err := trackerLoad.GetState(key) 1057 require.NoError(t, err) 1058 require.True(t, bytes.Equal(state1, state2)) 1059 } 1060 1061 func TestCheckpoint(t *testing.T) { 1062 tracker := setupDefaultTrackerForTest(t) 1063 1064 b, err := tracker.Checkpoint() 1065 require.NoError(t, err) 1066 1067 ctrl := gomock.NewController(t) 1068 teams := mocks.NewMockTeams(ctrl) 1069 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1070 broker := bmocks.NewMockBroker(ctrl) 1071 collateralService := mocks.NewMockCollateral(ctrl) 1072 trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1073 1074 require.NoError(t, trackerLoad.Load(context.Background(), b)) 1075 1076 bLoad, err := trackerLoad.Checkpoint() 1077 require.NoError(t, err) 1078 require.True(t, bytes.Equal(b, bLoad)) 1079 } 1080 1081 func TestSnapshotRoundTripViaEngine(t *testing.T) { 1082 transfersM5 := []*types.Transfer{ 1083 {Owner: "party3", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 1084 {Owner: "party3", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 1085 {Owner: "party3", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 1086 } 1087 transfersM6 := []*types.Transfer{ 1088 {Owner: "party4", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(500)}}, 1089 {Owner: "party4", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(1500)}}, 1090 {Owner: "party4", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(1500)}}, 1091 } 1092 1093 ctx := vgtest.VegaContext("chainid", 100) 1094 tracker1 := setupDefaultTrackerForTest(t) 1095 now := time.Now() 1096 log := logging.NewTestLogger() 1097 timeService := stubs.NewTimeStub() 1098 timeService.SetTime(now) 1099 statsData := stats.New(log, stats.NewDefaultConfig()) 1100 config := snp.DefaultConfig() 1101 vegaPath := paths.New(t.TempDir()) 1102 1103 snapshotEngine1, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain) 1104 require.NoError(t, err) 1105 snapshotEngine1CloseFn := vgtest.OnlyOnce(snapshotEngine1.Close) 1106 defer snapshotEngine1CloseFn() 1107 1108 snapshotEngine1.AddProviders(tracker1) 1109 1110 require.NoError(t, snapshotEngine1.Start(ctx)) 1111 1112 hash1, err := snapshotEngine1.SnapshotNow(ctx) 1113 require.NoError(t, err) 1114 1115 tracker1.SetEligibilityChecker(&EligibilityChecker{}) 1116 tracker1.MarketProposed("asset1", "market5", "meeeee") 1117 tracker1.MarketProposed("asset2", "market6", "meeeeeee") 1118 tracker1.UpdateFeesFromTransfers("asset1", "market5", transfersM5) 1119 tracker1.UpdateFeesFromTransfers("asset2", "market6", transfersM6) 1120 1121 state1 := map[string][]byte{} 1122 for _, key := range tracker1.Keys() { 1123 state, additionalProvider, err := tracker1.GetState(key) 1124 require.NoError(t, err) 1125 assert.Empty(t, additionalProvider) 1126 state1[key] = state 1127 } 1128 1129 snapshotEngine1CloseFn() 1130 1131 ctrl := gomock.NewController(t) 1132 teams := mocks.NewMockTeams(ctrl) 1133 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1134 broker := bmocks.NewMockBroker(ctrl) 1135 collateralService := mocks.NewMockCollateral(ctrl) 1136 tracker2 := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1137 snapshotEngine2, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain) 1138 require.NoError(t, err) 1139 defer snapshotEngine2.Close() 1140 1141 snapshotEngine2.AddProviders(tracker2) 1142 1143 // This triggers the state restoration from the local snapshot. 1144 require.NoError(t, snapshotEngine2.Start(ctx)) 1145 1146 // Comparing the hash after restoration, to ensure it produces the same result. 1147 hash2, _, _ := snapshotEngine2.Info() 1148 require.Equal(t, hash1, hash2) 1149 1150 tracker2.MarketProposed("asset1", "market5", "meeeee") 1151 tracker2.MarketProposed("asset2", "market6", "meeeeeee") 1152 tracker2.UpdateFeesFromTransfers("asset1", "market5", transfersM5) 1153 tracker2.UpdateFeesFromTransfers("asset2", "market6", transfersM6) 1154 1155 state2 := map[string][]byte{} 1156 for _, key := range tracker2.Keys() { 1157 state, additionalProvider, err := tracker2.GetState(key) 1158 require.NoError(t, err) 1159 assert.Empty(t, additionalProvider) 1160 state2[key] = state 1161 } 1162 1163 for key := range state1 { 1164 assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key) 1165 } 1166 } 1167 1168 func TestMarketProposerBonusScenarios(t *testing.T) { 1169 epochService := &TestEpochEngine{} 1170 ctrl := gomock.NewController(t) 1171 teams := mocks.NewMockTeams(ctrl) 1172 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1173 broker := bmocks.NewMockBroker(ctrl) 1174 collateralService := mocks.NewMockCollateral(ctrl) 1175 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1176 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 1177 tracker.SetEligibilityChecker(&EligibilityChecker{}) 1178 1179 // setup 4 market for settlement asset1 2 of them proposed by the same proposer, and 2 markets for settlement asset 2 1180 tracker.MarketProposed("asset1", "market1", "me") 1181 tracker.MarketProposed("asset1", "market2", "me") 1182 tracker.MarketProposed("asset1", "market3", "me2") 1183 tracker.MarketProposed("asset1", "market4", "me3") 1184 tracker.MarketProposed("asset2", "market5", "me") 1185 tracker.MarketProposed("asset2", "market6", "me2") 1186 1187 // no trading done so far so expect no one to be eligible for bonus 1188 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "zohar", []string{}))) 1189 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset2", []string{}, "VEGA", "zohar", []string{}))) 1190 1191 // market1 goes above the threshold only it should be eligible 1192 tracker.AddValueTraded("asset1", "market1", num.NewUint(5001)) 1193 require.Equal(t, 1, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{}))) 1194 require.Equal(t, 1, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"me"}))) 1195 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"not me"}))) 1196 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1197 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1198 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1199 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1200 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{"market1", "market2", "market3"}, "zohar") 1201 1202 // now market 2 and 3 become eligible 1203 tracker.AddValueTraded("asset1", "market2", num.NewUint(5001)) 1204 tracker.AddValueTraded("asset1", "market3", num.NewUint(5001)) 1205 require.Equal(t, 2, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{}))) 1206 require.Equal(t, 2, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"me", "me2"}))) 1207 require.Equal(t, 1, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"me", "not me"}))) 1208 require.Equal(t, 1, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"me2", "not me"}))) 1209 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{"not me"}))) 1210 1211 // show that only markets 2 and 3 are now eligible with this combo 1212 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1213 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1214 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1215 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1216 tracker.MarkPaidProposer("asset1", "market2", "VEGA", []string{"market1", "market2", "market3"}, "zohar") 1217 tracker.MarkPaidProposer("asset1", "market3", "VEGA", []string{"market1", "market2", "market3"}, "zohar") 1218 1219 // now market4 goes above the threshold but no one gets paid by this combo 1220 tracker.AddValueTraded("asset1", "market4", num.NewUint(5001)) 1221 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market2", "market3"}, "VEGA", "zohar", []string{}))) 1222 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1223 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1224 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1225 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{"market1", "market2", "market3"}, "zohar")) 1226 1227 // now "all" is funded by zohar 1228 require.Equal(t, 4, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "zohar", []string{}))) 1229 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 1230 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 1231 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{}, "zohar")) 1232 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{}, "zohar")) 1233 1234 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{}, "zohar") 1235 tracker.MarkPaidProposer("asset1", "market2", "VEGA", []string{}, "zohar") 1236 tracker.MarkPaidProposer("asset1", "market3", "VEGA", []string{}, "zohar") 1237 tracker.MarkPaidProposer("asset1", "market4", "VEGA", []string{}, "zohar") 1238 1239 // everyone were paid so next time no one is eligible 1240 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "zohar", []string{}))) 1241 1242 // a new market is proposed and gets over the limit 1243 tracker.MarketProposed("asset1", "market7", "mememe") 1244 tracker.AddValueTraded("asset1", "market7", num.NewUint(5001)) 1245 1246 // only the new market should be eligible for the "all" combo funded by zohar 1247 require.Equal(t, 1, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "zohar", []string{}))) 1248 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "zohar")) 1249 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "zohar")) 1250 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{}, "zohar")) 1251 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{}, "zohar")) 1252 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market7", "VEGA", []string{}, "zohar")) 1253 tracker.MarkPaidProposer("asset1", "market7", "VEGA", []string{}, "zohar") 1254 1255 // check that they are no longer eligible for this combo of all 1256 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "zohar", []string{}))) 1257 1258 // check new combo 1259 require.Equal(t, 3, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market3", "market7"}, "VEGA", "zohar", []string{}))) 1260 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{"market1", "market3", "market7"}, "zohar")) 1261 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{"market1", "market3", "market7"}, "zohar")) 1262 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{"market1", "market3", "market7"}, "zohar")) 1263 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{"market1", "market3", "market7"}, "zohar")) 1264 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market7", "VEGA", []string{"market1", "market3", "market7"}, "zohar")) 1265 1266 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{"market1", "market3", "market7"}, "zohar") 1267 tracker.MarkPaidProposer("asset1", "market3", "VEGA", []string{"market1", "market3", "market7"}, "zohar") 1268 tracker.MarkPaidProposer("asset1", "market7", "VEGA", []string{"market1", "market3", "market7"}, "zohar") 1269 1270 // now that they're marked as paid check they're no longer eligible 1271 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market3", "market7"}, "VEGA", "zohar", []string{}))) 1272 1273 // check new asset for the same combo 1274 require.Equal(t, 3, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market3", "market7"}, "USDC", "zohar", []string{}))) 1275 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "USDC", []string{"market1", "market3", "market7"}, "zohar")) 1276 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "USDC", []string{"market1", "market3", "market7"}, "zohar")) 1277 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "USDC", []string{"market1", "market3", "market7"}, "zohar")) 1278 require.False(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "USDC", []string{"market1", "market3", "market7"}, "zohar")) 1279 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market7", "USDC", []string{"market1", "market3", "market7"}, "zohar")) 1280 1281 tracker.MarkPaidProposer("asset1", "market1", "USDC", []string{"market1", "market3", "market7"}, "zohar") 1282 tracker.MarkPaidProposer("asset1", "market3", "USDC", []string{"market1", "market3", "market7"}, "zohar") 1283 tracker.MarkPaidProposer("asset1", "market7", "USDC", []string{"market1", "market3", "market7"}, "zohar") 1284 1285 // now that they're marked as paid check they're no longer eligible 1286 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{"market1", "market3", "market7"}, "USDC", "zohar", []string{}))) 1287 1288 // check new funder for the all combo 1289 require.Equal(t, 5, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "jeremy", []string{}))) 1290 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market1", "VEGA", []string{}, "jeremy")) 1291 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market2", "VEGA", []string{}, "jeremy")) 1292 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market3", "VEGA", []string{}, "jeremy")) 1293 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market4", "VEGA", []string{}, "jeremy")) 1294 require.True(t, tracker.IsMarketEligibleForBonus("asset1", "market7", "VEGA", []string{}, "jeremy")) 1295 1296 tracker.MarkPaidProposer("asset1", "market1", "VEGA", []string{}, "jeremy") 1297 tracker.MarkPaidProposer("asset1", "market2", "VEGA", []string{}, "jeremy") 1298 tracker.MarkPaidProposer("asset1", "market3", "VEGA", []string{}, "jeremy") 1299 tracker.MarkPaidProposer("asset1", "market4", "VEGA", []string{}, "jeremy") 1300 tracker.MarkPaidProposer("asset1", "market7", "VEGA", []string{}, "jeremy") 1301 require.Equal(t, 0, len(tracker.GetMarketsWithEligibleProposer("asset1", []string{}, "VEGA", "jeremy", []string{}))) 1302 } 1303 1304 func TestNotionalMetric(t *testing.T) { 1305 epochService := &TestEpochEngine{} 1306 ctx := context.Background() 1307 ctrl := gomock.NewController(t) 1308 teams := mocks.NewMockTeams(ctrl) 1309 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1310 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 1311 broker := bmocks.NewMockBroker(ctrl) 1312 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 1313 broker.EXPECT().Send(gomock.Any()).AnyTimes() 1314 collateralService := mocks.NewMockCollateral(ctrl) 1315 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1316 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 1317 1318 epochStartTime := time.Now() 1319 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime}) 1320 tracker.SetEligibilityChecker(&EligibilityChecker{}) 1321 tracker.MarketProposed("a1", "m1", "p1") 1322 1323 // 100 seconds into the epoch record a position of 100 1324 tracker.RecordPosition("a1", "p1", "m1", 100, num.NewUint(1), num.DecimalOne(), epochStartTime.Add(100*time.Second)) 1325 1326 // 200 seconds later record another position 1327 // pBar = 100 * 300/400 = 75 1328 tracker.RecordPosition("a1", "p1", "m1", -200, num.NewUint(2), num.DecimalOne(), epochStartTime.Add(400*time.Second)) 1329 1330 // now end the epoch after 600 seconds 1331 // pBar = (1 - 600/1000) * 75 + 200 * 600/1000 = 150 1332 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime, EndTime: epochStartTime.Add(1000 * time.Second)}) 1333 1334 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1335 require.Equal(t, 1, len(scores)) 1336 require.Equal(t, "p1", scores[0].Party) 1337 require.Equal(t, "0.000027", scores[0].Score.String()) 1338 1339 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1340 require.Equal(t, 1, len(scores)) 1341 require.Equal(t, "p1", scores[0].Party) 1342 require.Equal(t, "0.0000135", scores[0].Score.String()) 1343 1344 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 1345 require.Equal(t, 1, len(scores)) 1346 require.Equal(t, "p1", scores[0].Party) 1347 require.Equal(t, "0.000009", scores[0].Score.String()) 1348 1349 // qualifying the market to m1, expect the same result 1350 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m1"}}) 1351 require.Equal(t, 1, len(scores)) 1352 require.Equal(t, "p1", scores[0].Party) 1353 require.Equal(t, "0.000009", scores[0].Score.String()) 1354 1355 // qualifying the market to m2, expect the same result 1356 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m2"}}) 1357 require.Equal(t, 0, len(scores)) 1358 1359 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime.Add(1000 * time.Second)}) 1360 1361 // 600 seconds into the epoch new position is recorded for p1 1362 // pBar = 0 * 150 + 1 * 200 = 200 1363 tracker.RecordPosition("a1", "p1", "m1", 100, num.NewUint(3), num.DecimalOne(), epochStartTime.Add(1600*time.Second)) 1364 1365 // end the epoch 1366 // pBar = 0.6 * 200 + 0.4 * 100 = 160 1367 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime.Add(1000 * time.Second), EndTime: epochStartTime.Add(2000 * time.Second)}) 1368 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1369 require.Equal(t, 1, len(scores)) 1370 require.Equal(t, "p1", scores[0].Party) 1371 require.Equal(t, "0.000036", scores[0].Score.String()) 1372 1373 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1374 require.Equal(t, 1, len(scores)) 1375 require.Equal(t, "p1", scores[0].Party) 1376 require.Equal(t, "0.0000315", scores[0].Score.String()) 1377 1378 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5}) 1379 require.Equal(t, 1, len(scores)) 1380 require.Equal(t, "p1", scores[0].Party) 1381 require.Equal(t, "0.0000126", scores[0].Score.String()) 1382 1383 // qualify to m1 1384 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5, Markets: []string{"m1"}}) 1385 require.Equal(t, 1, len(scores)) 1386 require.Equal(t, "p1", scores[0].Party) 1387 require.Equal(t, "0.0000126", scores[0].Score.String()) 1388 1389 // qualify to m2 1390 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5, Markets: []string{"m2"}}) 1391 require.Equal(t, 0, len(scores)) 1392 1393 // now lets lets at an epoch with no activity 1394 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime.Add(2000 * time.Second)}) 1395 // end the epoch 1396 // pBar = 0 * 160 + 1 * 100 = 100 1397 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime.Add(2000 * time.Second), EndTime: epochStartTime.Add(3000 * time.Second)}) 1398 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1399 require.Equal(t, 1, len(scores)) 1400 require.Equal(t, "p1", scores[0].Party) 1401 require.Equal(t, "0.00003", scores[0].Score.String()) 1402 1403 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1404 require.Equal(t, 1, len(scores)) 1405 require.Equal(t, "p1", scores[0].Party) 1406 require.Equal(t, "0.000033", scores[0].Score.String()) 1407 1408 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 4}) 1409 require.Equal(t, 1, len(scores)) 1410 require.Equal(t, "p1", scores[0].Party) 1411 require.Equal(t, "0.00002325", scores[0].Score.String()) 1412 } 1413 1414 func TestRealisedReturnMetric(t *testing.T) { 1415 epochService := &TestEpochEngine{} 1416 ctx := context.Background() 1417 ctrl := gomock.NewController(t) 1418 teams := mocks.NewMockTeams(ctrl) 1419 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1420 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 1421 broker := bmocks.NewMockBroker(ctrl) 1422 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 1423 broker.EXPECT().Send(gomock.Any()).AnyTimes() 1424 collateralService := mocks.NewMockCollateral(ctrl) 1425 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1426 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 1427 1428 epochStartTime := time.Now() 1429 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime}) 1430 tracker.SetEligibilityChecker(&EligibilityChecker{}) 1431 tracker.MarketProposed("a1", "m1", "p1") 1432 1433 tracker.RecordFundingPayment("a1", "p1", "m1", num.NewDecimalFromFloat(100)) 1434 tracker.RecordFundingPayment("a1", "p1", "m1", num.NewDecimalFromFloat(-200)) 1435 tracker.RecordRealisedPosition("a1", "p1", "m1", num.DecimalFromFloat(130)) 1436 1437 // now end the epoch after 600 seconds 1438 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime, EndTime: epochStartTime.Add(1000 * time.Second)}) 1439 1440 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1441 require.Equal(t, 1, len(scores)) 1442 require.Equal(t, "p1", scores[0].Party) 1443 require.Equal(t, "30", scores[0].Score.String()) 1444 1445 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1446 require.Equal(t, 1, len(scores)) 1447 require.Equal(t, "p1", scores[0].Party) 1448 require.Equal(t, "15", scores[0].Score.String()) 1449 1450 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 1451 require.Equal(t, 1, len(scores)) 1452 require.Equal(t, "p1", scores[0].Party) 1453 require.Equal(t, "10", scores[0].Score.String()) 1454 1455 // qualifying the market to m1, expect the same result 1456 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m1"}}) 1457 require.Equal(t, 1, len(scores)) 1458 require.Equal(t, "p1", scores[0].Party) 1459 require.Equal(t, "10", scores[0].Score.String()) 1460 1461 // qualifying the market to m2, expect the same result 1462 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m2"}}) 1463 require.Equal(t, 0, len(scores)) 1464 1465 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime.Add(1000 * time.Second)}) 1466 1467 tracker.RecordRealisedPosition("a1", "p1", "m1", num.DecimalFromFloat(-45)) 1468 1469 // end the epoch 1470 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime.Add(1000 * time.Second), EndTime: epochStartTime.Add(2000 * time.Second)}) 1471 1472 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1473 require.Equal(t, 1, len(scores)) 1474 require.Equal(t, "p1", scores[0].Party) 1475 require.Equal(t, "-45", scores[0].Score.String()) 1476 1477 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1478 require.Equal(t, 1, len(scores)) 1479 require.Equal(t, "p1", scores[0].Party) 1480 require.Equal(t, "-7.5", scores[0].Score.String()) 1481 1482 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5}) 1483 require.Equal(t, 1, len(scores)) 1484 require.Equal(t, "p1", scores[0].Party) 1485 require.Equal(t, "-3", scores[0].Score.String()) 1486 1487 // qualify to m1 1488 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5, Markets: []string{"m1"}}) 1489 require.Equal(t, 1, len(scores)) 1490 require.Equal(t, "p1", scores[0].Party) 1491 require.Equal(t, "-3", scores[0].Score.String()) 1492 1493 // qualify to m2 1494 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 5, Markets: []string{"m2"}}) 1495 require.Equal(t, 0, len(scores)) 1496 1497 // now lets lets at an epoch with no activity 1498 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime.Add(2000 * time.Second)}) 1499 // end the epoch 1500 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime.Add(2000 * time.Second), EndTime: epochStartTime.Add(3000 * time.Second)}) 1501 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1502 require.Equal(t, 1, len(scores)) 1503 require.Equal(t, "p1", scores[0].Party) 1504 require.Equal(t, "0", scores[0].Score.String()) 1505 require.Equal(t, false, scores[0].IsEligible) 1506 1507 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1508 require.Equal(t, 1, len(scores)) 1509 require.Equal(t, "p1", scores[0].Party) 1510 require.Equal(t, "-22.5", scores[0].Score.String()) 1511 1512 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 4}) 1513 require.Equal(t, 1, len(scores)) 1514 require.Equal(t, "p1", scores[0].Party) 1515 require.Equal(t, "-3.75", scores[0].Score.String()) 1516 } 1517 1518 func TestRelativeReturnMetric(t *testing.T) { 1519 ctx := context.Background() 1520 epochService := &TestEpochEngine{} 1521 ctrl := gomock.NewController(t) 1522 teams := mocks.NewMockTeams(ctrl) 1523 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1524 balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() 1525 1526 broker := bmocks.NewMockBroker(ctrl) 1527 broker.EXPECT().Send(gomock.Any()).AnyTimes() 1528 broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 1529 collateralService := mocks.NewMockCollateral(ctrl) 1530 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1531 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 1532 1533 epochStartTime := time.Now() 1534 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime}) 1535 tracker.SetEligibilityChecker(&EligibilityChecker{}) 1536 tracker.MarketProposed("a1", "m1", "p1") 1537 1538 tracker.RecordPosition("a1", "p1", "m1", 100, num.NewUint(1), num.DecimalOne(), epochStartTime.Add(100*time.Second)) 1539 tracker.RecordPosition("a1", "p1", "m1", -200, num.NewUint(2), num.DecimalOne(), epochStartTime.Add(400*time.Second)) 1540 1541 tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(100)) 1542 tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(-120)) 1543 tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(150)) 1544 tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(-115)) 1545 1546 // end the epoch 1547 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime, EndTime: epochStartTime.Add(1000 * time.Second)}) 1548 1549 // the total m2m = 15 1550 // the time weighted position for the epoch = 150 1551 // therefore the r = 15/150 = 0.1 1552 1553 // window size 1 1554 scores := tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1555 require.Equal(t, 1, len(scores)) 1556 require.Equal(t, "p1", scores[0].Party) 1557 require.Equal(t, "0.1", scores[0].Score.String()) 1558 1559 // window size 2 1560 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1561 require.Equal(t, 1, len(scores)) 1562 require.Equal(t, "p1", scores[0].Party) 1563 require.Equal(t, "0.05", scores[0].Score.String()) 1564 1565 // window size 3 1566 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3}) 1567 require.Equal(t, 1, len(scores)) 1568 require.Equal(t, "p1", scores[0].Party) 1569 require.Equal(t, "0.0333333333333333", scores[0].Score.String()) 1570 1571 // add only this market in scope, expect same result 1572 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m1"}}) 1573 require.Equal(t, 1, len(scores)) 1574 require.Equal(t, "p1", scores[0].Party) 1575 require.Equal(t, "0.0333333333333333", scores[0].Score.String()) 1576 1577 // add market scope with the wrong market: 1578 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m2"}}) 1579 require.Equal(t, 0, len(scores)) 1580 1581 // add a scope with a different market, expect nothing back 1582 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 3, Markets: []string{"m2"}}) 1583 require.Equal(t, 0, len(scores)) 1584 1585 // lets run another epoch and make some loss 1586 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime.Add(1000 * time.Second)}) 1587 tracker.RecordPosition("a1", "p1", "m1", 100, num.NewUint(2), num.DecimalOne(), epochStartTime.Add(1600*time.Second)) 1588 tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(-8)) 1589 1590 // end the epoch 1591 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: epochStartTime.Add(1000 * time.Second), EndTime: epochStartTime.Add(2000 * time.Second)}) 1592 1593 // total m2m = -8 1594 // the time weighted position for the epoch = 160 1595 // therefore r = -0.05 1596 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 1}) 1597 require.Equal(t, 1, len(scores)) 1598 require.Equal(t, "p1", scores[0].Party) 1599 require.Equal(t, "-0.05", scores[0].Score.String()) 1600 1601 // with window size=2 we get (0.1-0.05)/2 = 0.025 1602 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 2}) 1603 require.Equal(t, 1, len(scores)) 1604 require.Equal(t, "p1", scores[0].Party) 1605 require.Equal(t, "0.025", scores[0].Score.String()) 1606 1607 // with window size=4 we get (0.1-0.05)/4 = 0.0125 1608 scores = tracker.CalculateMetricForIndividuals(ctx, &vgproto.DispatchStrategy{AssetForMetric: "a1", Metric: vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN, IndividualScope: vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL, WindowLength: 4}) 1609 require.Equal(t, 1, len(scores)) 1610 require.Equal(t, "p1", scores[0].Party) 1611 require.Equal(t, "0.0125", scores[0].Score.String()) 1612 } 1613 1614 func TestTeamStatsForMarkets(t *testing.T) { 1615 ctrl := gomock.NewController(t) 1616 teams := mocks.NewMockTeams(ctrl) 1617 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1618 broker := bmocks.NewMockBroker(ctrl) 1619 collateralService := mocks.NewMockCollateral(ctrl) 1620 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1621 1622 asset1 := vgrand.RandomStr(5) 1623 asset2 := vgrand.RandomStr(5) 1624 asset3 := vgrand.RandomStr(5) 1625 1626 market1 := "market1" 1627 market2 := "market2" 1628 market3 := "market3" 1629 market4 := "market4" 1630 market5 := "market5" 1631 market6 := "market6" 1632 market7 := "market7" 1633 1634 team1 := "team1" 1635 team2 := "team2" 1636 member11 := "member11" 1637 member12 := "member12" 1638 member21 := "member21" 1639 member22 := "member22" 1640 lonewolf1 := "lone-wolf1" 1641 lonewolf2 := "lone-wolf2" 1642 1643 // 1. Need markets with different assets. 1644 tracker.MarketProposed(asset1, market1, vgrand.RandomStr(5)) 1645 tracker.MarketProposed(asset1, market2, vgrand.RandomStr(5)) 1646 tracker.MarketProposed(asset1, market3, vgrand.RandomStr(5)) 1647 1648 tracker.MarketProposed(asset2, market4, vgrand.RandomStr(5)) 1649 tracker.MarketProposed(asset2, market5, vgrand.RandomStr(5)) 1650 1651 tracker.MarketProposed(asset3, market6, vgrand.RandomStr(5)) 1652 tracker.MarketProposed(asset3, market7, vgrand.RandomStr(5)) 1653 1654 // 2. Need to define teams. 1655 teams.EXPECT().GetAllTeamsWithParties(uint64(0)).Return(map[string][]string{ 1656 team1: {member11, member12}, 1657 team2: {member21, member22}, 1658 }).Times(1) 1659 1660 // 3. Need parties generating volume on these markets. 1661 tracker.RecordNotionalTakerVolume(market1, member11, num.NewUint(1)) 1662 tracker.RecordNotionalTakerVolume(market1, member12, num.NewUint(2)) 1663 tracker.RecordNotionalTakerVolume(market1, member21, num.NewUint(3)) 1664 tracker.RecordNotionalTakerVolume(market1, member22, num.NewUint(4)) 1665 tracker.RecordNotionalTakerVolume(market1, lonewolf1, num.NewUint(5)) 1666 tracker.RecordNotionalTakerVolume(market1, lonewolf2, num.NewUint(6)) 1667 1668 tracker.RecordNotionalTakerVolume(market2, member11, num.NewUint(1)) 1669 tracker.RecordNotionalTakerVolume(market2, member12, num.NewUint(2)) 1670 tracker.RecordNotionalTakerVolume(market2, member21, num.NewUint(3)) 1671 tracker.RecordNotionalTakerVolume(market2, member22, num.NewUint(4)) 1672 tracker.RecordNotionalTakerVolume(market2, lonewolf1, num.NewUint(5)) 1673 tracker.RecordNotionalTakerVolume(market2, lonewolf2, num.NewUint(6)) 1674 1675 // No participation of team 2 in market 3. 1676 tracker.RecordNotionalTakerVolume(market3, member11, num.NewUint(1)) 1677 tracker.RecordNotionalTakerVolume(market3, member12, num.NewUint(2)) 1678 tracker.RecordNotionalTakerVolume(market3, lonewolf1, num.NewUint(5)) 1679 tracker.RecordNotionalTakerVolume(market3, lonewolf2, num.NewUint(6)) 1680 1681 // No participation of team 1 in market 4. 1682 tracker.RecordNotionalTakerVolume(market4, member21, num.NewUint(3)) 1683 tracker.RecordNotionalTakerVolume(market4, member22, num.NewUint(4)) 1684 tracker.RecordNotionalTakerVolume(market4, lonewolf1, num.NewUint(5)) 1685 tracker.RecordNotionalTakerVolume(market4, lonewolf2, num.NewUint(6)) 1686 1687 // Market 5 is not expected to be filtered on, so none of these volume 1688 // should show up in the stats. 1689 tracker.RecordNotionalTakerVolume(market5, member11, num.NewUint(1000)) 1690 tracker.RecordNotionalTakerVolume(market5, member12, num.NewUint(2000)) 1691 tracker.RecordNotionalTakerVolume(market5, member12, num.NewUint(3000)) 1692 tracker.RecordNotionalTakerVolume(market5, member22, num.NewUint(4000)) 1693 tracker.RecordNotionalTakerVolume(market5, lonewolf1, num.NewUint(5000)) 1694 tracker.RecordNotionalTakerVolume(market5, lonewolf2, num.NewUint(6000)) 1695 1696 // Nobody likes market 6. So, no participation of any kind. 1697 1698 // Only lone-wolves in market 7. 1699 tracker.RecordNotionalTakerVolume(market7, lonewolf1, num.NewUint(5)) 1700 tracker.RecordNotionalTakerVolume(market7, lonewolf2, num.NewUint(6)) 1701 1702 // Regarding the dataset above, this should result in gathering the data from 1703 // the market 1, 2, 3, 4, and 7, but not 5 and 6, because: 1704 // - we want all markets from asset 1 -> market 1, 2, and 3. 1705 // - we want specific market 1, 3, 4, and 7. 1706 // 1707 // NB: It's on purpose we have duplicated references to the market 1 and 3, so 1708 // we can ensure it's duplicated and we don't add up stats from a market multiple 1709 // times. 1710 teamsStats := tracker.TeamStatsForMarkets([]string{asset1}, []string{market1, market3, market4, market7}) 1711 1712 assert.Equal(t, map[string]map[string]*num.Uint{ 1713 team1: { 1714 member11: num.NewUint(3), 1715 member12: num.NewUint(6), 1716 }, 1717 team2: { 1718 member21: num.NewUint(9), 1719 member22: num.NewUint(12), 1720 }, 1721 }, teamsStats) 1722 } 1723 1724 func setupDefaultTrackerForTest(t *testing.T) *common.MarketActivityTracker { 1725 t.Helper() 1726 1727 epochService := &TestEpochEngine{} 1728 ctrl := gomock.NewController(t) 1729 teams := mocks.NewMockTeams(ctrl) 1730 balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) 1731 broker := bmocks.NewMockBroker(ctrl) 1732 collateralService := mocks.NewMockCollateral(ctrl) 1733 tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) 1734 epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) 1735 1736 epochStartTime := time.Now() 1737 epochService.target(context.Background(), types.Epoch{Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: epochStartTime}) 1738 tracker.SetEligibilityChecker(&EligibilityChecker{}) 1739 1740 tracker.MarketProposed("asset1", "market1", "me") 1741 tracker.MarketProposed("asset1", "market2", "me2") 1742 tracker.MarketProposed("asset1", "market4", "me4") 1743 tracker.MarketProposed("asset2", "market3", "me3") 1744 1745 tracker.RecordPosition("asset1", "p1", "market1", 100, num.NewUint(1), num.DecimalOne(), time.Now()) 1746 tracker.RecordPosition("asset1", "p1", "market2", 200, num.NewUint(2), num.DecimalOne(), time.Now()) 1747 tracker.RecordPosition("asset1", "p2", "market1", 300, num.NewUint(3), num.DecimalOne(), time.Now()) 1748 tracker.RecordPosition("asset1", "p3", "market2", 400, num.NewUint(4), num.DecimalOne(), time.Now()) 1749 tracker.RecordPosition("asset1", "p3", "market4", 500, num.NewUint(5), num.DecimalOne(), time.Now()) 1750 tracker.RecordPosition("asset2", "p4", "market3", 600, num.NewUint(6), num.DecimalOne(), time.Now()) 1751 1752 tracker.RecordM2M("asset1", "p1", "market1", num.DecimalOne()) 1753 tracker.RecordM2M("asset1", "p1", "market2", num.DecimalFromInt64(5)) 1754 1755 tracker.RecordNotionalTakerVolume("market1", "p1", num.NewUint(10)) 1756 tracker.RecordNotionalTakerVolume("market1", "p2", num.NewUint(10)) 1757 tracker.RecordNotionalTakerVolume("market1", "p3", num.NewUint(10)) 1758 tracker.RecordNotionalTakerVolume("market1", "p4", num.NewUint(10)) 1759 tracker.RecordNotionalTakerVolume("market2", "p1", num.NewUint(20)) 1760 tracker.RecordNotionalTakerVolume("market2", "p2", num.NewUint(20)) 1761 tracker.RecordNotionalTakerVolume("market2", "p3", num.NewUint(20)) 1762 tracker.RecordNotionalTakerVolume("market2", "p4", num.NewUint(20)) 1763 tracker.RecordNotionalTakerVolume("market3", "p1", num.NewUint(30)) 1764 tracker.RecordNotionalTakerVolume("market3", "p2", num.NewUint(30)) 1765 tracker.RecordNotionalTakerVolume("market3", "p3", num.NewUint(30)) 1766 tracker.RecordNotionalTakerVolume("market3", "p4", num.NewUint(30)) 1767 tracker.RecordNotionalTakerVolume("market4", "p1", num.NewUint(40)) 1768 tracker.RecordNotionalTakerVolume("market4", "p2", num.NewUint(40)) 1769 tracker.RecordNotionalTakerVolume("market4", "p3", num.NewUint(40)) 1770 tracker.RecordNotionalTakerVolume("market4", "p4", num.NewUint(40)) 1771 1772 // update with a few transfers 1773 transfersM1 := []*types.Transfer{ 1774 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, 1775 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 1776 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 1777 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, 1778 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(300)}}, 1779 {Owner: "party1", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 1780 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, 1781 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(800)}}, 1782 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(700)}}, 1783 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, 1784 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(200)}}, 1785 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1000)}}, 1786 } 1787 tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) 1788 1789 transfersM2 := []*types.Transfer{ 1790 {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(500)}}, 1791 {Owner: "party2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 1792 {Owner: "party2", Type: types.TransferTypeLiquidityFeeNetDistribute, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(1500)}}, 1793 } 1794 tracker.UpdateFeesFromTransfers("asset2", "market2", transfersM2) 1795 1796 transfersM3 := []*types.Transfer{ 1797 {Owner: "party1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(500)}}, 1798 {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(450)}}, 1799 } 1800 tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM3) 1801 1802 return tracker 1803 }