code.vegaprotocol.io/vega@v0.79.0/core/banking/recurring_transfers_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 banking_test 17 18 import ( 19 "context" 20 "encoding/hex" 21 "fmt" 22 "testing" 23 24 "code.vegaprotocol.io/vega/core/assets" 25 "code.vegaprotocol.io/vega/core/banking" 26 "code.vegaprotocol.io/vega/core/events" 27 "code.vegaprotocol.io/vega/core/types" 28 "code.vegaprotocol.io/vega/libs/crypto" 29 "code.vegaprotocol.io/vega/libs/num" 30 "code.vegaprotocol.io/vega/libs/proto" 31 "code.vegaprotocol.io/vega/protos/vega" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 ) 37 38 func TestRecurringTransfers(t *testing.T) { 39 t.Run("recurring invalid transfers", testRecurringTransferInvalidTransfers) 40 t.Run("valid recurring transfers", testValidRecurringTransfer) 41 t.Run("valid forever transfers, cancelled not enough funds", testForeverTransferCancelledNotEnoughFunds) 42 t.Run("invalid recurring transfers, duplicates", testInvalidRecurringTransfersDuplicates) 43 t.Run("invalid recurring transfers, bad amount", testInvalidRecurringTransfersBadAmount) 44 t.Run("invalid recurring transfers, in the past", testInvalidRecurringTransfersInThePast) 45 } 46 47 func TestExpireOldTransfers(t *testing.T) { 48 e := getTestEngine(t) 49 50 ctx := context.Background() 51 52 e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1)) 53 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(10)}), nil) 54 e.broker.EXPECT().Send(gomock.Any()).AnyTimes() 55 fromAcc := types.Account{ 56 Balance: num.NewUint(100000), // enough for the all 57 } 58 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil) 59 60 endEpoch := uint64(12) 61 transfers := []*types.TransferFunds{} 62 for i := 0; i < 10; i++ { 63 transfers = append(transfers, &types.TransferFunds{ 64 Kind: types.TransferCommandKindRecurring, 65 Recurring: &types.RecurringTransfer{ 66 TransferBase: &types.TransferBase{ 67 ID: fmt.Sprintf("TRANSFERID-%d", i), 68 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 69 FromAccountType: types.AccountTypeGeneral, 70 To: crypto.RandomHash(), 71 ToAccountType: types.AccountTypeGeneral, 72 Asset: assetNameETH, 73 Amount: num.NewUint(10), 74 Reference: "someref", 75 }, 76 StartEpoch: 10, 77 EndEpoch: &endEpoch, 78 Factor: num.MustDecimalFromString("1"), 79 }, 80 }) 81 require.NoError(t, e.TransferFunds(ctx, transfers[i])) 82 } 83 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() 84 85 seenEvts := []events.Event{} 86 e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) { 87 seenEvts = append(seenEvts, evts...) 88 }).AnyTimes() 89 90 e.OnEpoch(context.Background(), types.Epoch{Seq: 15, Action: vega.EpochAction_EPOCH_ACTION_START}) 91 e.OnEpoch(context.Background(), types.Epoch{Seq: 15, Action: vega.EpochAction_EPOCH_ACTION_END}) 92 93 require.Equal(t, 10, len(seenEvts)) 94 stoppedIDs := map[string]struct{}{} 95 for _, e2 := range seenEvts { 96 if e2.StreamMessage().GetTransfer().Status == types.TransferStatusDone { 97 stoppedIDs[e2.StreamMessage().GetTransfer().Id] = struct{}{} 98 } 99 } 100 require.Equal(t, 10, len(stoppedIDs)) 101 } 102 103 func TestMaturation(t *testing.T) { 104 e := getTestEngine(t) 105 106 ctx := context.Background() 107 108 e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1)) 109 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(10)}), nil) 110 e.broker.EXPECT().Send(gomock.Any()).AnyTimes() 111 fromAcc := types.Account{ 112 Balance: num.NewUint(100000), // enough for the all 113 } 114 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil) 115 116 endEpoch := uint64(12) 117 transfers := []*types.TransferFunds{} 118 for i := 0; i < 10; i++ { 119 transfers = append(transfers, &types.TransferFunds{ 120 Kind: types.TransferCommandKindRecurring, 121 Recurring: &types.RecurringTransfer{ 122 TransferBase: &types.TransferBase{ 123 ID: fmt.Sprintf("TRANSFERID-%d", i), 124 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 125 FromAccountType: types.AccountTypeGeneral, 126 To: crypto.RandomHash(), 127 ToAccountType: types.AccountTypeGeneral, 128 Asset: assetNameETH, 129 Amount: num.NewUint(10), 130 Reference: "someref", 131 }, 132 StartEpoch: 10, 133 EndEpoch: &endEpoch, 134 Factor: num.MustDecimalFromString("1"), 135 }, 136 }) 137 require.NoError(t, e.TransferFunds(ctx, transfers[i])) 138 } 139 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() 140 141 seenEvts := []events.Event{} 142 e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) { 143 seenEvts = append(seenEvts, evts...) 144 }).AnyTimes() 145 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START}) 146 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END}) 147 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START}) 148 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END}) 149 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START}) 150 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END}) 151 e.OnEpoch(context.Background(), types.Epoch{Seq: 13, Action: vega.EpochAction_EPOCH_ACTION_START}) 152 e.OnEpoch(context.Background(), types.Epoch{Seq: 13, Action: vega.EpochAction_EPOCH_ACTION_END}) 153 154 require.Equal(t, 10, len(seenEvts)) 155 stoppedIDs := map[string]struct{}{} 156 for _, e2 := range seenEvts { 157 if e2.StreamMessage().GetTransfer().Status == types.TransferStatusDone { 158 stoppedIDs[e2.StreamMessage().GetTransfer().Id] = struct{}{} 159 } 160 } 161 require.Equal(t, 10, len(stoppedIDs)) 162 } 163 164 func testInvalidRecurringTransfersBadAmount(t *testing.T) { 165 e := getTestEngine(t) 166 167 ctx := context.Background() 168 transfer := &types.TransferFunds{ 169 Kind: types.TransferCommandKindRecurring, 170 Recurring: &types.RecurringTransfer{ 171 TransferBase: &types.TransferBase{ 172 ID: "TRANSFERID", 173 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 174 FromAccountType: types.AccountTypeGeneral, 175 To: "0000000000000000000000000000000000000000000000000000000000000000", 176 ToAccountType: types.AccountTypeGlobalReward, 177 Asset: assetNameETH, 178 Amount: num.NewUint(10), 179 Reference: "someref", 180 }, 181 StartEpoch: 10, 182 Factor: num.MustDecimalFromString("0.9"), 183 }, 184 } 185 186 e.OnMinTransferQuantumMultiple(context.Background(), num.DecimalFromFloat(1)) 187 // asset exists 188 e.assets.EXPECT().Get(gomock.Any()).Times(1).Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 189 e.broker.EXPECT().Send(gomock.Any()).Times(1) 190 191 assert.EqualError(t, 192 e.TransferFunds(ctx, transfer), 193 "could not transfer funds, less than minimal amount requested to transfer", 194 ) 195 } 196 197 func testInvalidRecurringTransfersInThePast(t *testing.T) { 198 e := getTestEngine(t) 199 200 // let's do a massive fee, easy to test 201 e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) 202 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START}) 203 204 var endEpoch13 uint64 = 11 205 ctx := context.Background() 206 transfer := &types.TransferFunds{ 207 Kind: types.TransferCommandKindRecurring, 208 Recurring: &types.RecurringTransfer{ 209 TransferBase: &types.TransferBase{ 210 ID: "TRANSFERID", 211 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 212 FromAccountType: types.AccountTypeGeneral, 213 To: "0000000000000000000000000000000000000000000000000000000000000000", 214 ToAccountType: types.AccountTypeGlobalReward, 215 Asset: assetNameETH, 216 Amount: num.NewUint(100), 217 Reference: "someref", 218 }, 219 StartEpoch: 6, 220 EndEpoch: &endEpoch13, 221 Factor: num.MustDecimalFromString("0.9"), 222 }, 223 } 224 225 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 226 e.broker.EXPECT().Send(gomock.Any()).Times(1) 227 assert.EqualError(t, 228 e.TransferFunds(ctx, transfer), 229 "start epoch in the past", 230 ) 231 232 // now all should be fine, let's try to start another same transfer use the current epoch 233 234 transfer2 := &types.TransferFunds{ 235 Kind: types.TransferCommandKindRecurring, 236 Recurring: &types.RecurringTransfer{ 237 TransferBase: &types.TransferBase{ 238 ID: "TRANSFERID2", 239 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 240 FromAccountType: types.AccountTypeGeneral, 241 To: "0000000000000000000000000000000000000000000000000000000000000000", 242 ToAccountType: types.AccountTypeGlobalReward, 243 Asset: assetNameETH, 244 Amount: num.NewUint(50), 245 Reference: "someotherref", 246 }, 247 StartEpoch: 7, 248 Factor: num.MustDecimalFromString("0.9"), 249 }, 250 } 251 252 e.broker.EXPECT().Send(gomock.Any()).Times(1) 253 assert.NoError(t, 254 e.TransferFunds(ctx, transfer2), 255 ) 256 } 257 258 func testInvalidRecurringTransfersDuplicates(t *testing.T) { 259 e := getTestEngine(t) 260 261 // let's do a massive fee, easy to test 262 e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) 263 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START}) 264 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END}) 265 266 var endEpoch13 uint64 = 11 267 ctx := context.Background() 268 transfer := &types.TransferFunds{ 269 Kind: types.TransferCommandKindRecurring, 270 Recurring: &types.RecurringTransfer{ 271 TransferBase: &types.TransferBase{ 272 ID: "TRANSFERID", 273 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 274 FromAccountType: types.AccountTypeGeneral, 275 To: "0000000000000000000000000000000000000000000000000000000000000000", 276 ToAccountType: types.AccountTypeGlobalReward, 277 Asset: assetNameETH, 278 Amount: num.NewUint(100), 279 Reference: "someref", 280 }, 281 StartEpoch: 10, 282 EndEpoch: &endEpoch13, 283 Factor: num.MustDecimalFromString("0.9"), 284 }, 285 } 286 287 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 288 e.broker.EXPECT().Send(gomock.Any()).Times(1) 289 assert.NoError(t, e.TransferFunds(ctx, transfer)) 290 291 // now all should be fine, let's try to start another same transfer 292 293 transfer2 := &types.TransferFunds{ 294 Kind: types.TransferCommandKindRecurring, 295 Recurring: &types.RecurringTransfer{ 296 TransferBase: &types.TransferBase{ 297 ID: "TRANSFERID2", 298 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 299 FromAccountType: types.AccountTypeGeneral, 300 To: "0000000000000000000000000000000000000000000000000000000000000000", 301 ToAccountType: types.AccountTypeGlobalReward, 302 Asset: assetNameETH, 303 Amount: num.NewUint(50), 304 Reference: "someotherref", 305 }, 306 StartEpoch: 15, 307 Factor: num.MustDecimalFromString("0.9"), 308 }, 309 } 310 311 e.broker.EXPECT().Send(gomock.Any()).Times(1) 312 assert.EqualError(t, 313 e.TransferFunds(ctx, transfer2), 314 banking.ErrCannotSubmitDuplicateRecurringTransferWithSameFromAndTo.Error(), 315 ) 316 317 // same from/to different asset - should pass 318 transfer3 := &types.TransferFunds{ 319 Kind: types.TransferCommandKindRecurring, 320 Recurring: &types.RecurringTransfer{ 321 TransferBase: &types.TransferBase{ 322 ID: "TRANSFERID3", 323 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 324 FromAccountType: types.AccountTypeGeneral, 325 To: "0000000000000000000000000000000000000000000000000000000000000000", 326 ToAccountType: types.AccountTypeGlobalReward, 327 Asset: "VEGA", 328 Amount: num.NewUint(50), 329 Reference: "someotherref", 330 }, 331 StartEpoch: 15, 332 Factor: num.MustDecimalFromString("0.9"), 333 }, 334 } 335 e.broker.EXPECT().Send(gomock.Any()).Times(1) 336 assert.NoError(t, e.TransferFunds(ctx, transfer3)) 337 } 338 339 func testForeverTransferCancelledNotEnoughFunds(t *testing.T) { 340 e := getTestEngine(t) 341 342 // let's do a massive fee, easy to test 343 e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) 344 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START}) 345 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END}) 346 347 ctx := context.Background() 348 transfer := &types.TransferFunds{ 349 Kind: types.TransferCommandKindRecurring, 350 Recurring: &types.RecurringTransfer{ 351 TransferBase: &types.TransferBase{ 352 ID: "TRANSFERID", 353 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 354 FromAccountType: types.AccountTypeGeneral, 355 To: "0000000000000000000000000000000000000000000000000000000000000000", 356 ToAccountType: types.AccountTypeGlobalReward, 357 Asset: assetNameETH, 358 Amount: num.NewUint(100), 359 Reference: "someref", 360 }, 361 DispatchStrategy: &vega.DispatchStrategy{ 362 Metric: vega.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED, 363 EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS, 364 }, 365 StartEpoch: 10, 366 EndEpoch: nil, // forever 367 Factor: num.MustDecimalFromString("0.9"), 368 }, 369 } 370 371 e.marketActivityTracker.EXPECT().CalculateMetricForIndividuals(gomock.Any(), gomock.Any()).AnyTimes().Return([]*types.PartyContributionScore{ 372 {Party: "", Score: num.DecimalFromFloat(1), StakingBalance: num.UintZero(), OpenVolume: num.UintZero(), TotalFeesPaid: num.UintZero(), IsEligible: true}, 373 }) 374 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 375 e.broker.EXPECT().Send(gomock.Any()).AnyTimes() 376 assert.NoError(t, e.TransferFunds(ctx, transfer)) 377 378 // now let's move epochs to see the others transfers 379 // first 2 epochs nothing happen 380 e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_START}) 381 e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END}) 382 e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START}) 383 e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_END}) 384 // now we are in business 385 386 fromAcc := types.Account{ 387 Balance: num.NewUint(160), // enough for the first transfer 388 } 389 390 // asset exists 391 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(2).Return(&fromAcc, nil) 392 393 // assert the calculation of fees and transfer request are correct 394 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn( 395 func(ctx context.Context, 396 transfers []*types.Transfer, 397 accountTypes []types.AccountType, 398 references []string, 399 feeTransfers []*types.Transfer, 400 feeTransfersAccountTypes []types.AccountType, 401 ) ([]*types.LedgerMovement, error, 402 ) { 403 t.Run("ensure transfers are correct", func(t *testing.T) { 404 // transfer is done fully instantly, we should have 2 transfer 405 assert.Len(t, transfers, 2) 406 assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 407 assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(100)) 408 assert.Equal(t, transfers[0].Amount.Asset, assetNameETH) 409 410 // 1 account types too 411 assert.Len(t, accountTypes, 2) 412 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 413 }) 414 415 t.Run("ensure fee transfers are correct", func(t *testing.T) { 416 assert.Len(t, feeTransfers, 1) 417 assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 418 assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(50)) 419 assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH) 420 421 // then the fees account types 422 assert.Len(t, feeTransfersAccountTypes, 1) 423 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 424 }) 425 426 return nil, nil 427 }) 428 429 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START}) 430 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END}) 431 432 fromAcc = types.Account{ 433 Balance: num.NewUint(10), // not enough for the second transfer 434 } 435 436 // asset exists 437 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil) 438 439 e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) { 440 t.Run("ensure transfer is stopped", func(t *testing.T) { 441 assert.Len(t, evts, 1) 442 e, ok := evts[0].(*events.TransferFunds) 443 assert.True(t, ok, "unexpected event from the bus") 444 assert.Equal(t, types.TransferStatusStopped, e.Proto().Status) 445 assert.Equal(t, "could not pay the fee for transfer: not enough funds to transfer", *e.Proto().Reason) 446 }) 447 }) 448 449 // ensure it's not called 450 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) 451 452 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START}) 453 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END}) 454 455 // then nothing happen, we are done 456 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START}) 457 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END}) 458 } 459 460 func testValidRecurringTransfer(t *testing.T) { 461 e := getTestEngine(t) 462 463 // let's do a massive fee, easy to test 464 e.OnTransferFeeFactorUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) 465 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_START}) 466 e.OnEpoch(context.Background(), types.Epoch{Seq: 7, Action: vega.EpochAction_EPOCH_ACTION_END}) 467 468 var endEpoch13 uint64 = 11 469 ctx := context.Background() 470 transfer := &types.TransferFunds{ 471 Kind: types.TransferCommandKindRecurring, 472 Recurring: &types.RecurringTransfer{ 473 TransferBase: &types.TransferBase{ 474 ID: "TRANSFERID", 475 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 476 FromAccountType: types.AccountTypeGeneral, 477 To: "0000000000000000000000000000000000000000000000000000000000000000", 478 ToAccountType: types.AccountTypeGlobalReward, 479 Asset: assetNameETH, 480 Amount: num.NewUint(100), 481 Reference: "someref", 482 }, 483 StartEpoch: 10, 484 EndEpoch: &endEpoch13, 485 Factor: num.MustDecimalFromString("0.9"), 486 }, 487 } 488 489 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 490 e.broker.EXPECT().Send(gomock.Any()).Times(3) 491 assert.NoError(t, e.TransferFunds(ctx, transfer)) 492 493 // now let's move epochs to see the others transfers 494 // first 2 epochs nothing happen 495 e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_START}) 496 e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END}) 497 e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START}) 498 e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_END}) 499 // now we are in business 500 501 fromAcc := types.Account{ 502 Balance: num.NewUint(1000), 503 } 504 505 // asset exists 506 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil) 507 508 // assert the calculation of fees and transfer request are correct 509 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn( 510 func(ctx context.Context, 511 transfers []*types.Transfer, 512 accountTypes []types.AccountType, 513 references []string, 514 feeTransfers []*types.Transfer, 515 feeTransfersAccountTypes []types.AccountType, 516 ) ([]*types.LedgerMovement, error, 517 ) { 518 t.Run("ensure transfers are correct", func(t *testing.T) { 519 // transfer is done fully instantly, we should have 2 transfer 520 assert.Len(t, transfers, 2) 521 assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 522 assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(100)) 523 assert.Equal(t, transfers[0].Amount.Asset, assetNameETH) 524 525 // 1 account types too 526 assert.Len(t, accountTypes, 2) 527 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 528 }) 529 530 t.Run("ensure fee transfers are correct", func(t *testing.T) { 531 assert.Len(t, feeTransfers, 1) 532 assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 533 assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(50)) 534 assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH) 535 536 // then the fees account types 537 assert.Len(t, feeTransfersAccountTypes, 1) 538 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 539 }) 540 541 return nil, nil 542 }) 543 544 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_START}) 545 e.OnEpoch(context.Background(), types.Epoch{Seq: 10, Action: vega.EpochAction_EPOCH_ACTION_END}) 546 547 // asset exists 548 e.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).Times(1).Return(&fromAcc, nil) 549 550 // assert the calculation of fees and transfer request are correct 551 e.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn( 552 func(ctx context.Context, 553 transfers []*types.Transfer, 554 accountTypes []types.AccountType, 555 references []string, 556 feeTransfers []*types.Transfer, 557 feeTransfersAccountTypes []types.AccountType, 558 ) ([]*types.LedgerMovement, error, 559 ) { 560 t.Run("ensure transfers are correct", func(t *testing.T) { 561 // transfer is done fully instantly, we should have 2 transfer 562 assert.Len(t, transfers, 2) 563 assert.Equal(t, transfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 564 assert.Equal(t, transfers[0].Amount.Amount, num.NewUint(90)) 565 assert.Equal(t, transfers[0].Amount.Asset, assetNameETH) 566 567 // 1 account types too 568 assert.Len(t, accountTypes, 2) 569 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 570 }) 571 572 t.Run("ensure fee transfers are correct", func(t *testing.T) { 573 assert.Len(t, feeTransfers, 1) 574 assert.Equal(t, feeTransfers[0].Owner, "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301") 575 assert.Equal(t, feeTransfers[0].Amount.Amount, num.NewUint(45)) 576 assert.Equal(t, feeTransfers[0].Amount.Asset, assetNameETH) 577 578 // then the fees account types 579 assert.Len(t, feeTransfersAccountTypes, 1) 580 assert.Equal(t, accountTypes[0], types.AccountTypeGeneral) 581 }) 582 583 return nil, nil 584 }) 585 586 e.broker.EXPECT().SendBatch(gomock.Any()).DoAndReturn(func(evts []events.Event) { 587 t.Run("ensure transfer is done", func(t *testing.T) { 588 assert.Len(t, evts, 1) 589 e, ok := evts[0].(*events.TransferFunds) 590 assert.True(t, ok, "unexpected event from the bus") 591 assert.Equal(t, e.Proto().Status, types.TransferStatusDone) 592 }) 593 }) 594 595 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_START}) 596 e.OnEpoch(context.Background(), types.Epoch{Seq: 11, Action: vega.EpochAction_EPOCH_ACTION_END}) 597 598 // then nothing happen, we are done 599 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_START}) 600 e.OnEpoch(context.Background(), types.Epoch{Seq: 12, Action: vega.EpochAction_EPOCH_ACTION_END}) 601 } 602 603 func testRecurringTransferInvalidTransfers(t *testing.T) { 604 e := getTestEngine(t) 605 606 ctx := context.Background() 607 transfer := types.TransferFunds{ 608 Kind: types.TransferCommandKindRecurring, 609 Recurring: &types.RecurringTransfer{}, 610 } 611 612 transferBase := types.TransferBase{ 613 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 614 FromAccountType: types.AccountTypeGeneral, 615 To: "2e05fd230f3c9f4eaf0bdc5bfb7ca0c9d00278afc44637aab60da76653d7ccf0", 616 ToAccountType: types.AccountTypeGeneral, 617 Asset: assetNameETH, 618 Amount: num.NewUint(10), 619 Reference: "someref", 620 } 621 622 // asset exists 623 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 624 625 var baseCpy types.TransferBase 626 627 t.Run("invalid from account", func(t *testing.T) { 628 e.broker.EXPECT().Send(gomock.Any()).Times(1) 629 baseCpy := transferBase 630 transfer.Recurring.TransferBase = &baseCpy 631 transfer.Recurring.From = "" 632 assert.EqualError(t, 633 e.TransferFunds(ctx, &transfer), 634 types.ErrInvalidFromAccount.Error(), 635 ) 636 }) 637 638 t.Run("invalid to account", func(t *testing.T) { 639 e.broker.EXPECT().Send(gomock.Any()).Times(1) 640 baseCpy = transferBase 641 transfer.Recurring.TransferBase = &baseCpy 642 transfer.Recurring.To = "" 643 assert.EqualError(t, 644 e.TransferFunds(ctx, &transfer), 645 types.ErrInvalidToAccount.Error(), 646 ) 647 }) 648 649 t.Run("unsupported from account type", func(t *testing.T) { 650 e.broker.EXPECT().Send(gomock.Any()).Times(1) 651 baseCpy = transferBase 652 transfer.Recurring.TransferBase = &baseCpy 653 transfer.Recurring.FromAccountType = types.AccountTypeBond 654 assert.EqualError(t, 655 e.TransferFunds(ctx, &transfer), 656 types.ErrUnsupportedFromAccountType.Error(), 657 ) 658 }) 659 660 t.Run("unsuported to account type", func(t *testing.T) { 661 e.broker.EXPECT().Send(gomock.Any()).Times(1) 662 baseCpy = transferBase 663 transfer.Recurring.TransferBase = &baseCpy 664 transfer.Recurring.ToAccountType = types.AccountTypeBond 665 assert.EqualError(t, 666 e.TransferFunds(ctx, &transfer), 667 types.ErrUnsupportedToAccountType.Error(), 668 ) 669 }) 670 671 t.Run("zero funds transfer", func(t *testing.T) { 672 e.broker.EXPECT().Send(gomock.Any()).Times(1) 673 baseCpy = transferBase 674 transfer.Recurring.TransferBase = &baseCpy 675 transfer.Recurring.Amount = num.UintZero() 676 assert.EqualError(t, 677 e.TransferFunds(ctx, &transfer), 678 types.ErrCannotTransferZeroFunds.Error(), 679 ) 680 }) 681 682 var ( 683 endEpoch100 uint64 = 100 684 endEpoch0 uint64 685 endEpoch1 uint64 = 1 686 ) 687 // now testing the recurring specific stuff 688 baseCpy = transferBase 689 transfer.Recurring.TransferBase = &baseCpy 690 transfer.Recurring.EndEpoch = &endEpoch100 691 transfer.Recurring.StartEpoch = 90 692 transfer.Recurring.Factor = num.MustDecimalFromString("0.1") 693 694 t.Run("bad start time", func(t *testing.T) { 695 transfer.Recurring.StartEpoch = 0 696 e.broker.EXPECT().Send(gomock.Any()).Times(1) 697 698 assert.EqualError(t, 699 e.TransferFunds(ctx, &transfer), 700 types.ErrStartEpochIsZero.Error(), 701 ) 702 }) 703 704 t.Run("bad end time", func(t *testing.T) { 705 transfer.Recurring.StartEpoch = 90 706 transfer.Recurring.EndEpoch = &endEpoch0 707 e.broker.EXPECT().Send(gomock.Any()).Times(1) 708 709 assert.EqualError(t, 710 e.TransferFunds(ctx, &transfer), 711 types.ErrEndEpochIsZero.Error(), 712 ) 713 }) 714 715 t.Run("negative factor", func(t *testing.T) { 716 transfer.Recurring.EndEpoch = &endEpoch100 717 transfer.Recurring.Factor = num.MustDecimalFromString("-1") 718 e.broker.EXPECT().Send(gomock.Any()).Times(1) 719 720 assert.EqualError(t, 721 e.TransferFunds(ctx, &transfer), 722 types.ErrInvalidFactor.Error(), 723 ) 724 }) 725 726 t.Run("zero factor", func(t *testing.T) { 727 transfer.Recurring.Factor = num.MustDecimalFromString("0") 728 e.broker.EXPECT().Send(gomock.Any()).Times(1) 729 730 assert.EqualError(t, 731 e.TransferFunds(ctx, &transfer), 732 types.ErrInvalidFactor.Error(), 733 ) 734 }) 735 736 t.Run("start epoch after end epoch", func(t *testing.T) { 737 transfer.Recurring.Factor = num.MustDecimalFromString("1") 738 transfer.Recurring.EndEpoch = &endEpoch1 739 e.broker.EXPECT().Send(gomock.Any()).Times(1) 740 741 assert.EqualError(t, 742 e.TransferFunds(ctx, &transfer), 743 types.ErrStartEpochAfterEndEpoch.Error(), 744 ) 745 }) 746 747 t.Run("end epoch nil", func(t *testing.T) { 748 transfer.Recurring.EndEpoch = nil 749 e.broker.EXPECT().Send(gomock.Any()).Times(1) 750 751 assert.NoError(t, 752 e.TransferFunds(ctx, &transfer), 753 ) 754 }) 755 } 756 757 func TestMarketAssetMismatchRejectsTransfer(t *testing.T) { 758 eng := getTestEngine(t) 759 760 fromAcc := types.Account{ 761 Balance: num.NewUint(1000), 762 } 763 764 eng.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 765 eng.col.EXPECT().GetPartyGeneralAccount(gomock.Any(), gomock.Any()).AnyTimes().Return(&fromAcc, nil) 766 eng.broker.EXPECT().Send(gomock.Any()).AnyTimes() 767 eng.col.EXPECT().TransferFunds(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 768 769 recurring := &types.TransferFunds{ 770 Kind: types.TransferCommandKindRecurring, 771 Recurring: &types.RecurringTransfer{ 772 TransferBase: &types.TransferBase{ 773 From: "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301", 774 FromAccountType: types.AccountTypeGeneral, 775 To: "2e05fd230f3c9f4eaf0bdc5bfb7ca0c9d00278afc44637aab60da76653d7ccf0", 776 ToAccountType: types.AccountTypeGeneral, 777 Asset: assetNameETH, 778 Amount: num.NewUint(10), 779 Reference: "someref", 780 }, 781 StartEpoch: 10, 782 EndEpoch: nil, // forever 783 Factor: num.MustDecimalFromString("0.9"), 784 DispatchStrategy: &vega.DispatchStrategy{ 785 AssetForMetric: "zohar", 786 Metric: vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, 787 Markets: []string{"mmm"}, 788 EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS, 789 IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM, 790 WindowLength: 1, 791 LockPeriod: 1, 792 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK, 793 }, 794 }, 795 } 796 797 // if in-scope market has a different asset it is rejected 798 eng.marketActivityTracker.EXPECT().MarketTrackedForAsset(gomock.Any(), gomock.Any()).Times(1).Return(false) 799 require.Error(t, eng.TransferFunds(context.Background(), recurring)) 800 } 801 802 func TestDispatchStrategyRemoval(t *testing.T) { 803 e := getTestEngine(t) 804 805 dispatchStrat := &vega.DispatchStrategy{ 806 AssetForMetric: "zohar", 807 Metric: vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL, 808 Markets: []string{"mmm"}, 809 EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS, 810 IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM, 811 WindowLength: 1, 812 LockPeriod: 1, 813 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK, 814 } 815 816 p, err := proto.Marshal(dispatchStrat) 817 require.NoError(t, err) 818 dsHash := hex.EncodeToString(crypto.Hash(p)) 819 820 var endEpoch uint64 = 100 821 party := "03ae90688632c649c4beab6040ff5bd04dbde8efbf737d8673bbda792a110301" 822 ctx := context.Background() 823 transfer := &types.TransferFunds{ 824 Kind: types.TransferCommandKindRecurring, 825 Recurring: &types.RecurringTransfer{ 826 TransferBase: &types.TransferBase{ 827 ID: "TRANSFERID", 828 From: party, 829 FromAccountType: types.AccountTypeGeneral, 830 To: "0000000000000000000000000000000000000000000000000000000000000000", 831 ToAccountType: types.AccountTypeGlobalReward, 832 Asset: assetNameETH, 833 Amount: num.NewUint(100), 834 Reference: "someref", 835 }, 836 StartEpoch: 8, 837 EndEpoch: &endEpoch, 838 Factor: num.MustDecimalFromString("0.9"), 839 DispatchStrategy: dispatchStrat, 840 }, 841 } 842 843 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().Return(assets.NewAsset(&mockAsset{name: assetNameETH, quantum: num.DecimalFromFloat(100)}), nil) 844 e.broker.EXPECT().Send(gomock.Any()).AnyTimes() 845 e.marketActivityTracker.EXPECT().MarketTrackedForAsset(gomock.Any(), gomock.Any()).Times(1).Return(true) 846 assert.NoError(t, e.TransferFunds(ctx, transfer)) 847 848 // it exists 849 assert.NotNil(t, e.GetDispatchStrategy(dsHash)) 850 851 // now cancel 852 require.NoError(t, e.CancelTransferFunds(ctx, 853 &types.CancelTransferFunds{ 854 Party: party, 855 TransferID: "TRANSFERID", 856 }, 857 ), 858 ) 859 860 // it does not exist (secretly it does but has ref-count 0) 861 assert.Nil(t, e.GetDispatchStrategy(dsHash)) 862 863 // roll into the next epoch end 864 e.OnEpoch(context.Background(), types.Epoch{Seq: 8, Action: vega.EpochAction_EPOCH_ACTION_END}) 865 e.OnEpoch(context.Background(), types.Epoch{Seq: 9, Action: vega.EpochAction_EPOCH_ACTION_START}) 866 867 // still not there 868 assert.Nil(t, e.GetDispatchStrategy(dsHash)) 869 }