code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_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 governance_test 17 18 import ( 19 "context" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "strconv" 24 "testing" 25 "time" 26 27 "code.vegaprotocol.io/vega/core/assets" 28 "code.vegaprotocol.io/vega/core/assets/builtin" 29 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 30 "code.vegaprotocol.io/vega/core/datasource" 31 "code.vegaprotocol.io/vega/core/datasource/common" 32 dstypes "code.vegaprotocol.io/vega/core/datasource/common" 33 dsdefinition "code.vegaprotocol.io/vega/core/datasource/definition" 34 "code.vegaprotocol.io/vega/core/datasource/external/signedoracle" 35 "code.vegaprotocol.io/vega/core/events" 36 "code.vegaprotocol.io/vega/core/governance" 37 "code.vegaprotocol.io/vega/core/governance/mocks" 38 "code.vegaprotocol.io/vega/core/netparams" 39 "code.vegaprotocol.io/vega/core/types" 40 "code.vegaprotocol.io/vega/libs/num" 41 vgrand "code.vegaprotocol.io/vega/libs/rand" 42 "code.vegaprotocol.io/vega/logging" 43 datapb "code.vegaprotocol.io/vega/protos/vega/data/v1" 44 45 "github.com/golang/mock/gomock" 46 "github.com/stretchr/testify/assert" 47 "github.com/stretchr/testify/require" 48 ) 49 50 var errNoBalanceForParty = errors.New("no balance for party") 51 52 type tstEngine struct { 53 *governance.Engine 54 ctrl *gomock.Controller 55 accounts *mocks.MockStakingAccounts 56 tsvc *mocks.MockTimeService 57 broker *bmocks.MockBroker 58 witness *mocks.MockWitness 59 markets *mocks.MockMarkets 60 assets *mocks.MockAssets 61 netp *netparams.Store 62 proposalCounter uint // to streamline proposal generation 63 tokenBal map[string]uint64 // party > balance 64 els map[string]map[string]float64 // market > party > ELS 65 } 66 67 func TestSubmitProposals(t *testing.T) { 68 t.Run("Submitting a proposal with closing time too soon fails", testSubmittingProposalWithClosingTimeTooSoonFails) 69 t.Run("Submitting a proposal with internal time termination with closing time too soon fails", testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooSoonFails) 70 t.Run("Submitting a proposal with closing time too late fails", testSubmittingProposalWithClosingTimeTooLateFails) 71 t.Run("Submitting a proposal with internal time termination with closing time too late fails", testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooLateFails) 72 t.Run("Submitting a proposal with enactment time too soon fails", testSubmittingProposalWithEnactmentTimeTooSoonFails) 73 t.Run("Submitting a proposal with enactment time too late fails", testSubmittingProposalWithEnactmentTimeTooLateFails) 74 t.Run("Submitting a proposal with non-existing account fails", testSubmittingProposalWithNonExistingAccountFails) 75 t.Run("Submitting a proposal with internal time termination with non-existing account fails", testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails) 76 t.Run("Submitting a proposal without enough stake fails", testSubmittingProposalWithoutEnoughStakeFails) 77 t.Run("Submitting an update market proposal without enough stake and els fails", testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails) 78 t.Run("Submitting a proposal with internal time termination without enough stake fails", testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails) 79 80 t.Run("Submitting a time-triggered proposal for new market with termination time before enactment time fails", testSubmittingTimeTriggeredProposalNewMarketTerminationBeforeEnactmentFails) 81 82 t.Run("Voting on non-existing proposal fails", testVotingOnNonExistingProposalFails) 83 t.Run("Voting with non-existing account fails", testVotingWithNonExistingAccountFails) 84 t.Run("Voting without token fails", testVotingWithoutTokenFails) 85 86 t.Run("Test multiple proposal lifecycle", testMultipleProposalsLifecycle) 87 t.Run("Withdrawing vote assets removes vote from proposal state calculation", testWithdrawingVoteAssetRemovesVoteFromProposalStateCalculation) 88 89 t.Run("Updating voters key on votes succeeds", testUpdatingVotersKeyOnVotesSucceeds) 90 t.Run("Updating voters key on votes with internal time termination succeeds", testUpdatingVotersKeyOnVotesWithInternalTimeTerminationSucceeds) 91 92 t.Run("Computing the governance state hash is deterministic", testComputingGovernanceStateHashIsDeterministic) 93 t.Run("Submit proposal update market", testSubmitProposalMarketUpdate) 94 } 95 96 func testUpdatingVotersKeyOnVotesSucceeds(t *testing.T) { 97 eng := getTestEngine(t, time.Now()) 98 99 // given 100 proposer := vgrand.RandomStr(5) 101 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 102 103 // setup 104 eng.ensureAllAssetEnabled(t) 105 eng.ensureTokenBalanceForParty(t, proposer, 1) 106 107 // expect 108 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 109 110 // when 111 _, err := eng.submitProposal(t, proposal) 112 113 // then 114 require.NoError(t, err) 115 116 // given 117 voter1 := vgrand.RandomStr(5) 118 119 // setup 120 eng.ensureTokenBalanceForParty(t, voter1, 1) 121 122 // expect 123 eng.expectVoteEvent(t, voter1, proposal.ID) 124 125 // when 126 err = eng.addYesVote(t, voter1, proposal.ID) 127 128 // then 129 require.NoError(t, err) 130 131 // given 132 voter2 := vgrand.RandomStr(5) 133 134 // setup 135 eng.ensureTokenBalanceForParty(t, voter2, 1) 136 137 // expect 138 eng.expectVoteEvent(t, voter2, proposal.ID) 139 140 // when 141 err = eng.addNoVote(t, voter2, proposal.ID) 142 143 // then 144 require.NoError(t, err) 145 146 // given 147 newVoter1ID := vgrand.RandomStr(5) 148 149 // expect 150 eng.expectVoteEvent(t, newVoter1ID, proposal.ID) 151 152 // then 153 eng.ValidatorKeyChanged(context.Background(), voter1, newVoter1ID) 154 155 // given 156 newVoter2ID := vgrand.RandomStr(5) 157 158 // setup 159 eng.expectVoteEvent(t, newVoter2ID, proposal.ID) 160 161 // then 162 eng.ValidatorKeyChanged(context.Background(), voter2, newVoter2ID) 163 } 164 165 func testUpdatingVotersKeyOnVotesWithInternalTimeTerminationSucceeds(t *testing.T) { 166 eng := getTestEngine(t, time.Now()) 167 168 // given 169 proposer := vgrand.RandomStr(5) 170 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false) 171 172 // setup 173 eng.ensureAllAssetEnabled(t) 174 eng.ensureTokenBalanceForParty(t, proposer, 1) 175 176 // expect 177 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 178 179 // when 180 _, err := eng.submitProposal(t, proposal) 181 182 // then 183 require.NoError(t, err) 184 185 // given 186 voter1 := vgrand.RandomStr(5) 187 188 // setup 189 eng.ensureTokenBalanceForParty(t, voter1, 1) 190 191 // expect 192 eng.expectVoteEvent(t, voter1, proposal.ID) 193 194 // when 195 err = eng.addYesVote(t, voter1, proposal.ID) 196 197 // then 198 require.NoError(t, err) 199 200 // given 201 voter2 := vgrand.RandomStr(5) 202 203 // setup 204 eng.ensureTokenBalanceForParty(t, voter2, 1) 205 206 // expect 207 eng.expectVoteEvent(t, voter2, proposal.ID) 208 209 // when 210 err = eng.addNoVote(t, voter2, proposal.ID) 211 212 // then 213 require.NoError(t, err) 214 215 // given 216 newVoter1ID := vgrand.RandomStr(5) 217 218 // expect 219 eng.expectVoteEvent(t, newVoter1ID, proposal.ID) 220 221 // then 222 eng.ValidatorKeyChanged(context.Background(), voter1, newVoter1ID) 223 224 // given 225 newVoter2ID := vgrand.RandomStr(5) 226 227 // setup 228 eng.expectVoteEvent(t, newVoter2ID, proposal.ID) 229 230 // then 231 eng.ValidatorKeyChanged(context.Background(), voter2, newVoter2ID) 232 } 233 234 func testSubmittingProposalWithNonExistingAccountFails(t *testing.T) { 235 eng := getTestEngine(t, time.Now()) 236 237 // given 238 party := vgrand.RandomStr(5) 239 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 240 241 tcs := []struct { 242 name string 243 proposal types.Proposal 244 }{ 245 { 246 name: "For new market", 247 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 248 }, { 249 name: "For new asset", 250 proposal: eng.newProposalForNewAsset(party, now), 251 }, { 252 name: "Freeform", 253 proposal: eng.newFreeformProposal(party, now), 254 }, 255 } 256 257 for _, tc := range tcs { 258 t.Run(tc.name, func(tt *testing.T) { 259 // setup 260 eng.ensureAllAssetEnabled(tt) 261 eng.ensureNoAccountForParty(tt, party) 262 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) 263 264 // when 265 _, err := eng.submitProposal(tt, tc.proposal) 266 267 // then 268 require.Error(tt, err) 269 assert.EqualError(tt, err, errNoBalanceForParty.Error()) 270 }) 271 272 t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) { 273 // setup 274 eng.ensureAllAssetEnabled(tt) 275 eng.ensureNoAccountForParty(tt, party) 276 277 batchID := eng.newProposalID() 278 279 // expect 280 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 281 eng.expectProposalEvents(t, []expectedProposal{ 282 { 283 partyID: party, 284 proposalID: tc.proposal.ID, 285 state: types.ProposalStateRejected, 286 reason: types.ProposalErrorInsufficientTokens, 287 }, 288 }) 289 290 sub := eng.newBatchSubmission( 291 now.Add(48*time.Hour).Unix(), 292 tc.proposal, 293 ) 294 295 // when 296 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 297 298 // then 299 require.Error(tt, err) 300 assert.EqualError(tt, err, errNoBalanceForParty.Error()) 301 }) 302 } 303 } 304 305 func testSubmitProposalMarketUpdate(t *testing.T) { 306 eng := getTestEngine(t, time.Now()) 307 308 // given 309 party := vgrand.RandomStr(5) 310 marketID := vgrand.RandomStr(5) 311 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 312 tc := struct { 313 name string 314 proposal types.Proposal 315 }{ 316 name: "For market update", 317 proposal: eng.newProposalForMarketUpdate(marketID, party, now, nil, nil, true), 318 } 319 320 // test that with no account but equity like share, a market update proposal goes through 321 t.Run(tc.name, func(tt *testing.T) { 322 // setup 323 eng.ensureAllAssetEnabled(tt) 324 eng.ensureNoAccountForParty(tt, party) 325 eng.expectOpenProposalEvent(tt, party, tc.proposal.ID) 326 327 eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true) 328 eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalOne(), true) 329 eng.ensureGetMarketFuture(t, marketID) 330 // when 331 _, err := eng.submitProposal(tt, tc.proposal) 332 333 // then 334 require.NoError(tt, err) 335 }) 336 } 337 338 func testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails(t *testing.T) { 339 eng := getTestEngine(t, time.Now()) 340 341 // given 342 party := vgrand.RandomStr(5) 343 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 344 345 tcs := []struct { 346 name string 347 proposal types.Proposal 348 }{ 349 { 350 name: "For new market", 351 proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), 352 }, { 353 name: "For new asset", 354 proposal: eng.newProposalForNewAsset(party, now), 355 }, { 356 name: "Freeform", 357 proposal: eng.newFreeformProposal(party, now), 358 }, 359 } 360 361 for _, tc := range tcs { 362 t.Run(tc.name, func(tt *testing.T) { 363 // setup 364 eng.ensureAllAssetEnabled(tt) 365 eng.ensureNoAccountForParty(tt, party) 366 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) 367 368 // when 369 _, err := eng.submitProposal(tt, tc.proposal) 370 371 // then 372 require.Error(tt, err) 373 assert.EqualError(tt, err, errNoBalanceForParty.Error()) 374 }) 375 } 376 } 377 378 func testSubmittingProposalWithoutEnoughStakeFails(t *testing.T) { 379 eng := getTestEngine(t, time.Now()) 380 381 // given 382 party := vgrand.RandomStr(5) 383 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 384 385 tcs := []struct { 386 name string 387 minProposerBalanceParam string 388 proposal types.Proposal 389 }{ 390 { 391 name: "For new market", 392 minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance, 393 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 394 }, { 395 name: "For new asset", 396 minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance, 397 proposal: eng.newProposalForNewAsset(party, now), 398 }, { 399 name: "Freeform", 400 minProposerBalanceParam: netparams.GovernanceProposalFreeformMinProposerBalance, 401 proposal: eng.newFreeformProposal(party, now), 402 }, 403 } 404 405 for _, tc := range tcs { 406 t.Run(tc.name, func(tt *testing.T) { 407 // setup 408 eng.ensureTokenBalanceForParty(tt, party, 10) 409 eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") 410 eng.ensureAllAssetEnabled(tt) 411 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) 412 413 // when 414 _, err := eng.submitProposal(tt, tc.proposal) 415 416 // then 417 require.Error(tt, err) 418 assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") 419 }) 420 421 t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) { 422 // setup 423 eng.ensureAllAssetEnabled(tt) 424 eng.ensureNoAccountForParty(tt, party) 425 426 batchID := eng.newProposalID() 427 428 // expect 429 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 430 eng.expectProposalEvents(t, []expectedProposal{ 431 { 432 partyID: party, 433 proposalID: tc.proposal.ID, 434 state: types.ProposalStateRejected, 435 reason: types.ProposalErrorInsufficientTokens, 436 }, 437 }) 438 439 sub := eng.newBatchSubmission( 440 now.Add(48*time.Hour).Unix(), 441 tc.proposal, 442 ) 443 444 // when 445 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 446 447 // then 448 require.Error(tt, err) 449 assert.EqualError(tt, err, errNoBalanceForParty.Error()) 450 }) 451 } 452 } 453 454 func testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails(t *testing.T) { 455 eng := getTestEngine(t, time.Now()) 456 457 // given 458 party := vgrand.RandomStr(5) 459 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 460 461 tc := struct { 462 name string 463 minProposerBalanceParam string 464 proposal types.Proposal 465 }{ 466 name: "For market update", 467 minProposerBalanceParam: netparams.GovernanceProposalUpdateMarketMinProposerBalance, 468 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), 469 } 470 471 t.Run(tc.name, func(tt *testing.T) { 472 // setup 473 eng.ensureTokenBalanceForParty(tt, party, 10) 474 eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") 475 eng.ensureAllAssetEnabled(tt) 476 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) 477 478 // when 479 eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true) 480 eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalZero(), true) 481 _, err := eng.submitProposal(tt, tc.proposal) 482 483 // then 484 require.Error(tt, err) 485 assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") 486 }) 487 488 t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) { 489 // setup 490 eng.ensureTokenBalanceForParty(tt, party, 10) 491 eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") 492 eng.ensureAllAssetEnabled(tt) 493 494 // when 495 eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true) 496 eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalZero(), true) 497 batchID := eng.newProposalID() 498 499 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 500 eng.expectProposalEvents(t, []expectedProposal{ 501 { 502 partyID: party, 503 proposalID: tc.proposal.ID, 504 state: types.ProposalStateRejected, 505 reason: types.ProposalErrorInsufficientTokens, 506 }, 507 }) 508 509 sub := eng.newBatchSubmission( 510 now.Add(48*time.Hour).Unix(), 511 tc.proposal, 512 ) 513 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 514 515 // then 516 require.Error(tt, err) 517 assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") 518 }) 519 } 520 521 func testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails(t *testing.T) { 522 eng := getTestEngine(t, time.Now()) 523 524 // given 525 party := vgrand.RandomStr(5) 526 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 527 528 tcs := []struct { 529 name string 530 minProposerBalanceParam string 531 proposal types.Proposal 532 }{ 533 { 534 name: "For new market", 535 minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance, 536 proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), 537 }, { 538 name: "For new asset", 539 minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance, 540 proposal: eng.newProposalForNewAsset(party, now), 541 }, { 542 name: "Freeform", 543 minProposerBalanceParam: netparams.GovernanceProposalFreeformMinProposerBalance, 544 proposal: eng.newFreeformProposal(party, now), 545 }, 546 } 547 548 for _, tc := range tcs { 549 t.Run(tc.name, func(tt *testing.T) { 550 // setup 551 eng.ensureTokenBalanceForParty(tt, party, 10) 552 eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") 553 eng.ensureAllAssetEnabled(tt) 554 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) 555 556 // when 557 _, err := eng.submitProposal(tt, tc.proposal) 558 559 // then 560 require.Error(tt, err) 561 assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") 562 }) 563 564 t.Run(fmt.Sprintf("%s batch version", tc.name), func(tt *testing.T) { 565 // setup 566 eng.ensureTokenBalanceForParty(tt, party, 10) 567 eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") 568 eng.ensureAllAssetEnabled(tt) 569 570 // when 571 batchID := eng.newProposalID() 572 573 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 574 eng.expectProposalEvents(t, []expectedProposal{ 575 { 576 partyID: party, 577 proposalID: tc.proposal.ID, 578 state: types.ProposalStateRejected, 579 reason: types.ProposalErrorInsufficientTokens, 580 }, 581 }) 582 583 sub := eng.newBatchSubmission( 584 now.Add(48*time.Hour).Unix(), 585 tc.proposal, 586 ) 587 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 588 589 // then 590 require.Error(tt, err) 591 assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") 592 }) 593 } 594 } 595 596 func testSubmittingProposalWithClosingTimeTooSoonFails(t *testing.T) { 597 eng := getTestEngine(t, time.Now()) 598 599 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 600 party := vgrand.RandomStr(5) 601 602 cases := []struct { 603 msg string 604 proposal types.Proposal 605 }{ 606 { 607 msg: "For new market", 608 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 609 }, { 610 msg: "For market update", 611 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), 612 }, { 613 msg: "For new asset", 614 proposal: eng.newProposalForNewAsset(party, now), 615 }, 616 } 617 618 for _, tc := range cases { 619 t.Run(tc.msg, func(tt *testing.T) { 620 // given 621 tc.proposal.Terms.ClosingTimestamp = now.Unix() 622 623 // setup 624 eng.ensureAllAssetEnabled(tt) 625 626 // expect 627 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooSoon) 628 629 // when 630 _, err := eng.submitProposal(tt, tc.proposal) 631 632 // then 633 require.Error(tt, err) 634 assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >") 635 }) 636 637 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 638 // setup 639 eng.ensureAllAssetEnabled(tt) 640 641 batchID := eng.newProposalID() 642 643 // expect 644 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 645 eng.expectProposalEvents(t, []expectedProposal{ 646 { 647 partyID: party, 648 proposalID: tc.proposal.ID, 649 state: types.ProposalStateRejected, 650 reason: types.ProposalErrorCloseTimeTooSoon, 651 }, 652 }) 653 654 sub := eng.newBatchSubmission( 655 now.Unix(), 656 tc.proposal, 657 ) 658 659 // when 660 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 661 662 // then 663 require.Error(tt, err) 664 assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >") 665 }) 666 } 667 } 668 669 func testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooSoonFails(t *testing.T) { 670 eng := getTestEngine(t, time.Now()) 671 672 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 673 party := vgrand.RandomStr(5) 674 675 cases := []struct { 676 msg string 677 proposal types.Proposal 678 }{ 679 { 680 msg: "For new market", 681 proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), 682 }, { 683 msg: "For market update", 684 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, false), 685 }, { 686 msg: "For new asset", 687 proposal: eng.newProposalForNewAsset(party, now), 688 }, 689 } 690 691 for _, tc := range cases { 692 t.Run(tc.msg, func(tt *testing.T) { 693 // given 694 tc.proposal.Terms.ClosingTimestamp = now.Unix() 695 696 // setup 697 eng.ensureAllAssetEnabled(tt) 698 699 // expect 700 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooSoon) 701 702 // when 703 _, err := eng.submitProposal(tt, tc.proposal) 704 705 // then 706 require.Error(tt, err) 707 assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >") 708 }) 709 710 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 711 // setup 712 eng.ensureAllAssetEnabled(tt) 713 714 batchID := eng.newProposalID() 715 716 // expect 717 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 718 eng.expectProposalEvents(t, []expectedProposal{ 719 { 720 partyID: party, 721 proposalID: tc.proposal.ID, 722 state: types.ProposalStateRejected, 723 reason: types.ProposalErrorCloseTimeTooSoon, 724 }, 725 }) 726 727 sub := eng.newBatchSubmission( 728 now.Unix(), 729 tc.proposal, 730 ) 731 732 // when 733 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 734 735 // then 736 require.Error(tt, err) 737 assert.Contains(tt, err.Error(), "proposal closing time too soon, expected >") 738 }) 739 } 740 } 741 742 func testSubmittingProposalWithClosingTimeTooLateFails(t *testing.T) { 743 eng := getTestEngine(t, time.Now()) 744 745 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 746 party := vgrand.RandomStr(5) 747 748 cases := []struct { 749 msg string 750 proposal types.Proposal 751 }{ 752 { 753 msg: "For new market", 754 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 755 }, { 756 msg: "For market update", 757 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), 758 }, { 759 msg: "For new asset", 760 proposal: eng.newProposalForNewAsset(party, now), 761 }, 762 } 763 764 for _, tc := range cases { 765 t.Run(tc.msg, func(tt *testing.T) { 766 // given 767 tc.proposal.Terms.ClosingTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix() 768 769 // setup 770 eng.ensureAllAssetEnabled(tt) 771 772 // expect 773 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooLate) 774 775 // when 776 _, err := eng.submitProposal(tt, tc.proposal) 777 778 // then 779 require.Error(tt, err) 780 assert.Contains(tt, err.Error(), "proposal closing time too late, expected <") 781 }) 782 783 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 784 // setup 785 eng.ensureAllAssetEnabled(tt) 786 787 batchID := eng.newProposalID() 788 789 // expect 790 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 791 eng.expectProposalEvents(t, []expectedProposal{ 792 { 793 partyID: party, 794 proposalID: tc.proposal.ID, 795 state: types.ProposalStateRejected, 796 reason: types.ProposalErrorCloseTimeTooLate, 797 }, 798 }) 799 800 sub := eng.newBatchSubmission( 801 now.Add(3*365*24*time.Hour).Unix(), 802 tc.proposal, 803 ) 804 805 // when 806 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 807 808 // then 809 require.Error(tt, err) 810 assert.Contains(tt, err.Error(), "proposal closing time too late, expected <") 811 }) 812 } 813 } 814 815 func testSubmittingProposalWithInternalTimeTerminationWithClosingTimeTooLateFails(t *testing.T) { 816 eng := getTestEngine(t, time.Now()) 817 818 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 819 party := vgrand.RandomStr(5) 820 821 cases := []struct { 822 msg string 823 proposal types.Proposal 824 }{ 825 { 826 msg: "For new market", 827 proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), 828 }, { 829 msg: "For market update", 830 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, false), 831 }, { 832 msg: "For new asset", 833 proposal: eng.newProposalForNewAsset(party, now), 834 }, 835 } 836 837 for _, tc := range cases { 838 t.Run(tc.msg, func(tt *testing.T) { 839 // given 840 tc.proposal.Terms.ClosingTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix() 841 842 // setup 843 eng.ensureAllAssetEnabled(tt) 844 845 // expect 846 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorCloseTimeTooLate) 847 848 // when 849 _, err := eng.submitProposal(tt, tc.proposal) 850 851 // then 852 require.Error(tt, err) 853 assert.Contains(tt, err.Error(), "proposal closing time too late, expected <") 854 }) 855 856 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 857 // setup 858 eng.ensureAllAssetEnabled(tt) 859 860 batchID := eng.newProposalID() 861 862 // expect 863 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 864 eng.expectProposalEvents(t, []expectedProposal{ 865 { 866 partyID: party, 867 proposalID: tc.proposal.ID, 868 state: types.ProposalStateRejected, 869 reason: types.ProposalErrorCloseTimeTooLate, 870 }, 871 }) 872 873 sub := eng.newBatchSubmission( 874 now.Add(3*365*24*time.Hour).Unix(), 875 tc.proposal, 876 ) 877 878 // when 879 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 880 881 // then 882 require.Error(tt, err) 883 assert.Contains(tt, err.Error(), "proposal closing time too late, expected <") 884 }) 885 } 886 } 887 888 func testSubmittingTimeTriggeredProposalNewMarketTerminationBeforeEnactmentFails(t *testing.T) { 889 eng := getTestEngine(t, time.Now()) 890 891 proposer := vgrand.RandomStr(5) 892 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 893 894 // Make a proporsal with termination time before enactment time 895 // Enactment time for new market is now + 96 hours 896 termTimeBeforeEnact := now.Add(1 * 48 * time.Hour).Add(47 * time.Hour).Add(15 * time.Minute) 897 898 filter, binding := produceTimeTriggeredDataSourceSpec(termTimeBeforeEnact) 899 proposal1 := eng.newProposalForNewMarket(proposer, now, filter, binding, true) 900 901 eng.ensureTokenBalanceForParty(t, proposer, 1) 902 eng.ensureAllAssetEnabled(t) 903 904 eng.expectRejectedProposalEvent(t, proposer, proposal1.ID, types.ProposalErrorInvalidFutureProduct) 905 _, err := eng.submitProposal(t, proposal1) 906 require.Error(t, err) 907 assert.Contains(t, err.Error(), "data source spec termination time before enactment") 908 909 // Make another proposal with termination time same as enactment time 910 // Enactment time for new market is now + 96 hours 911 termTimeEqualEnact := now.Add(2 * 48 * time.Hour) 912 filter, binding = produceTimeTriggeredDataSourceSpec(termTimeEqualEnact) 913 proposal2 := eng.newProposalForNewMarket(proposer, now, filter, binding, true) 914 915 eng.ensureTokenBalanceForParty(t, proposer, 1) 916 eng.ensureAllAssetEnabled(t) 917 eng.expectRejectedProposalEvent(t, proposer, proposal2.ID, types.ProposalErrorInvalidFutureProduct) 918 919 _, err = eng.submitProposal(t, proposal2) 920 require.Error(t, err) 921 assert.Contains(t, err.Error(), "data source spec termination time before enactment") 922 } 923 924 func testSubmittingProposalWithEnactmentTimeTooSoonFails(t *testing.T) { 925 eng := getTestEngine(t, time.Now()) 926 927 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 928 party := vgrand.RandomStr(5) 929 930 cases := []struct { 931 msg string 932 proposal types.Proposal 933 }{ 934 { 935 msg: "For new market", 936 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 937 }, { 938 msg: "For market update", 939 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), 940 }, { 941 msg: "For new asset", 942 proposal: eng.newProposalForNewAsset(party, now), 943 }, 944 } 945 946 for _, tc := range cases { 947 t.Run(tc.msg, func(tt *testing.T) { 948 // given 949 tc.proposal.Terms.EnactmentTimestamp = now.Unix() 950 951 // setup 952 eng.ensureAllAssetEnabled(tt) 953 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorEnactTimeTooSoon) 954 955 // when 956 _, err := eng.submitProposal(tt, tc.proposal) 957 958 // then 959 require.Error(tt, err) 960 assert.Contains(tt, err.Error(), "proposal enactment time too soon, expected >") 961 }) 962 963 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 964 // given 965 tc.proposal.Terms.EnactmentTimestamp = now.Unix() 966 967 // setup 968 eng.ensureAllAssetEnabled(tt) 969 970 batchID := eng.newProposalID() 971 972 // expect 973 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 974 eng.expectProposalEvents(t, []expectedProposal{ 975 { 976 partyID: party, 977 proposalID: tc.proposal.ID, 978 state: types.ProposalStateRejected, 979 reason: types.ProposalErrorEnactTimeTooSoon, 980 }, 981 }) 982 983 sub := eng.newBatchSubmission( 984 now.Add(48*time.Hour).Unix(), 985 tc.proposal, 986 ) 987 988 // when 989 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 990 991 // then 992 require.Error(tt, err) 993 assert.Contains(tt, err.Error(), "proposal enactment time too soon, expected >") 994 }) 995 } 996 } 997 998 func testSubmittingProposalWithEnactmentTimeTooLateFails(t *testing.T) { 999 eng := getTestEngine(t, time.Now()) 1000 1001 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 1002 party := vgrand.RandomStr(5) 1003 1004 cases := []struct { 1005 msg string 1006 proposal types.Proposal 1007 }{ 1008 { 1009 msg: "For new market", 1010 proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), 1011 }, { 1012 msg: "For market update", 1013 proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), 1014 }, { 1015 msg: "For new asset", 1016 proposal: eng.newProposalForNewAsset(party, now), 1017 }, 1018 } 1019 1020 for _, tc := range cases { 1021 t.Run(tc.msg, func(tt *testing.T) { 1022 // given 1023 tc.proposal.Terms.EnactmentTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix() 1024 1025 // setup 1026 eng.ensureAllAssetEnabled(tt) 1027 1028 // expect 1029 eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorEnactTimeTooLate) 1030 1031 // when 1032 _, err := eng.submitProposal(tt, tc.proposal) 1033 1034 // then 1035 require.Error(tt, err) 1036 assert.Contains(tt, err.Error(), "proposal enactment time too late, expected <") 1037 }) 1038 1039 t.Run(fmt.Sprintf("%s batch version", tc.msg), func(tt *testing.T) { 1040 // given 1041 tc.proposal.Terms.EnactmentTimestamp = now.Add(3 * 365 * 24 * time.Hour).Unix() 1042 1043 // setup 1044 eng.ensureAllAssetEnabled(tt) 1045 1046 batchID := eng.newProposalID() 1047 1048 // expect 1049 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 1050 eng.expectProposalEvents(t, []expectedProposal{ 1051 { 1052 partyID: party, 1053 proposalID: tc.proposal.ID, 1054 state: types.ProposalStateRejected, 1055 reason: types.ProposalErrorEnactTimeTooLate, 1056 }, 1057 }) 1058 1059 sub := eng.newBatchSubmission( 1060 now.Add(48*time.Hour).Unix(), 1061 tc.proposal, 1062 ) 1063 1064 // when 1065 _, err := eng.submitBatchProposal(tt, sub, batchID, party) 1066 1067 // then 1068 require.Error(tt, err) 1069 assert.Contains(tt, err.Error(), "proposal enactment time too late, expected <") 1070 }) 1071 } 1072 } 1073 1074 func testVotingOnNonExistingProposalFails(t *testing.T) { 1075 eng := getTestEngine(t, time.Now()) 1076 1077 // when 1078 voter := vgrand.RandomStr(5) 1079 1080 // setup 1081 eng.ensureAllAssetEnabled(t) 1082 1083 // when 1084 err := eng.addYesVote(t, voter, vgrand.RandomStr(5)) 1085 1086 // then 1087 require.Error(t, err) 1088 assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error()) 1089 } 1090 1091 func testVotingWithNonExistingAccountFails(t *testing.T) { 1092 eng := getTestEngine(t, time.Now()) 1093 1094 // given 1095 proposer := vgrand.RandomStr(5) 1096 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1097 1098 // setup 1099 eng.ensureAllAssetEnabled(t) 1100 eng.ensureTokenBalanceForParty(t, proposer, 1) 1101 1102 // expect 1103 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1104 1105 // when 1106 _, err := eng.submitProposal(t, proposal) 1107 1108 // then 1109 require.NoError(t, err) 1110 1111 // given 1112 voterWithoutAccount := "voter-no-account" 1113 1114 // setup 1115 eng.ensureNoAccountForParty(t, voterWithoutAccount) 1116 1117 // when 1118 err = eng.addYesVote(t, voterWithoutAccount, proposal.ID) 1119 1120 // then 1121 require.Error(t, err) 1122 assert.EqualError(t, err, errNoBalanceForParty.Error()) 1123 } 1124 1125 func testVotingWithoutTokenFails(t *testing.T) { 1126 eng := getTestEngine(t, time.Now()) 1127 1128 // given 1129 proposer := eng.newValidParty("proposer", 1) 1130 proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1131 1132 // setup 1133 eng.ensureAllAssetEnabled(t) 1134 eng.expectOpenProposalEvent(t, proposer.Id, proposal.ID) 1135 1136 // when 1137 _, err := eng.submitProposal(t, proposal) 1138 1139 // then 1140 require.NoError(t, err) 1141 1142 // given 1143 voterWithEmptyAccount := vgrand.RandomStr(5) 1144 1145 // setup 1146 eng.ensureTokenBalanceForParty(t, voterWithEmptyAccount, 0) 1147 1148 // when 1149 err = eng.addYesVote(t, voterWithEmptyAccount, proposal.ID) 1150 1151 // then 1152 require.Error(t, err) 1153 assert.EqualError(t, err, governance.ErrVoterInsufficientTokens.Error()) 1154 } 1155 1156 func testMultipleProposalsLifecycle(t *testing.T) { 1157 eng := getTestEngine(t, time.Now()) 1158 1159 // given 1160 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 1161 partyA := vgrand.RandomStr(5) 1162 partyB := vgrand.RandomStr(5) 1163 1164 // setup 1165 eng.ensureAllAssetEnabled(t) 1166 eng.accounts.EXPECT().GetStakingAssetTotalSupply().AnyTimes().Return(num.NewUint(300)) 1167 eng.tokenBal[partyA] = 200 1168 eng.tokenBal[partyB] = 100 1169 1170 const howMany = 10 1171 passed := map[string]*types.Proposal{} 1172 declined := map[string]*types.Proposal{} 1173 var afterClosing time.Time 1174 var afterEnactment time.Time 1175 1176 for i := 0; i < howMany; i++ { 1177 toBePassed := eng.newProposalForNewMarket(partyA, now, nil, nil, true) 1178 eng.expectOpenProposalEvent(t, partyA, toBePassed.ID) 1179 _, err := eng.submitProposal(t, toBePassed) 1180 require.NoError(t, err) 1181 passed[toBePassed.ID] = &toBePassed 1182 1183 toBeDeclined := eng.newProposalForNewMarket(partyB, now, nil, nil, true) 1184 eng.expectOpenProposalEvent(t, partyB, toBeDeclined.ID) 1185 _, err = eng.submitProposal(t, toBeDeclined) 1186 require.NoError(t, err) 1187 declined[toBeDeclined.ID] = &toBeDeclined 1188 1189 if i == 0 { 1190 // all proposal terms are expected to be equal 1191 afterClosing = time.Unix(toBePassed.Terms.ClosingTimestamp, 0).Add(time.Second) 1192 afterEnactment = time.Unix(toBePassed.Terms.EnactmentTimestamp, 0).Add(time.Second) 1193 } 1194 } 1195 require.Len(t, passed, howMany) 1196 require.Len(t, declined, howMany) 1197 1198 for id := range passed { 1199 eng.expectVoteEvent(t, partyA, id) 1200 err := eng.addYesVote(t, partyA, id) 1201 require.NoError(t, err) 1202 1203 eng.expectVoteEvent(t, partyB, id) 1204 err = eng.addNoVote(t, partyB, id) 1205 require.NoError(t, err) 1206 } 1207 1208 for id := range declined { 1209 eng.expectVoteEvent(t, partyA, id) 1210 err := eng.addNoVote(t, partyA, id) 1211 require.NoError(t, err) 1212 1213 eng.expectVoteEvent(t, partyB, id) 1214 err = eng.addYesVote(t, partyB, id) 1215 require.NoError(t, err) 1216 } 1217 1218 var howManyPassed, howManyDeclined int 1219 eng.markets.EXPECT().GetMarketState(gomock.Any()).AnyTimes() 1220 eng.broker.EXPECT().Send(gomock.Any()).Times(howMany * 2).Do(func(evt events.Event) { 1221 pe, ok := evt.(*events.Proposal) 1222 require.True(t, ok) 1223 p := pe.Proposal() 1224 if p.State == types.ProposalStatePassed { 1225 _, found := passed[p.Id] 1226 assert.True(t, found, "passed proposal is in the passed collection") 1227 howManyPassed++ 1228 } else if p.State == types.ProposalStateDeclined { 1229 _, found := declined[p.Id] 1230 assert.True(t, found, "declined proposal is in the declined collection") 1231 howManyDeclined++ 1232 } else { 1233 assert.FailNow(t, "unexpected proposal state") 1234 } 1235 }) 1236 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(howMany * 2) 1237 eng.OnTick(context.Background(), afterClosing) 1238 assert.Equal(t, howMany, howManyPassed) 1239 assert.Equal(t, howMany, howManyDeclined) 1240 1241 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1242 require.Len(t, toBeEnacted, howMany) 1243 for i := 0; i < howMany; i++ { 1244 _, found := passed[toBeEnacted[i].Proposal().ID] 1245 assert.True(t, found) 1246 } 1247 } 1248 1249 func testWithdrawingVoteAssetRemovesVoteFromProposalStateCalculation(t *testing.T) { 1250 eng := getTestEngine(t, time.Now()) 1251 1252 // given 1253 proposer := vgrand.RandomStr(5) 1254 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1255 1256 // setup 1257 eng.ensureAllAssetEnabled(t) 1258 eng.ensureStakingAssetTotalSupply(t, 200) 1259 eng.ensureTokenBalanceForParty(t, proposer, 100) 1260 1261 // expect 1262 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1263 1264 // when 1265 _, err := eng.submitProposal(t, proposal) 1266 1267 // then 1268 require.NoError(t, err) 1269 1270 // given 1271 voter := vgrand.RandomStr(5) 1272 1273 // setup 1274 eng.ensureTokenBalanceForParty(t, voter, 100) 1275 1276 // expect 1277 eng.expectVoteEvent(t, voter, proposal.ID) 1278 1279 // when 1280 err = eng.addYesVote(t, voter, proposal.ID) 1281 1282 // then 1283 require.NoError(t, err) 1284 1285 // setup 1286 eng.ensureTokenBalanceForParty(t, voter, 100) 1287 1288 // expect 1289 eng.expectVoteEvent(t, voter, proposal.ID) 1290 1291 // when 1292 err = eng.addNoVote(t, voter, proposal.ID) 1293 1294 // then 1295 require.NoError(t, err) 1296 1297 // given 1298 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1299 1300 // setup 1301 eng.ensureTokenBalanceForParty(t, voter, 0) 1302 1303 // expect 1304 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached) 1305 eng.expectTotalGovernanceTokenFromVoteEvents(t, "0", "0") 1306 1307 // when 1308 eng.expectGetMarketState(t, proposal.ID) 1309 _, voteClosed := eng.OnTick(context.Background(), afterClosing) 1310 1311 // then 1312 require.Len(t, voteClosed, 1) 1313 vc := voteClosed[0] 1314 require.NotNil(t, vc.NewMarket()) 1315 assert.True(t, vc.NewMarket().Rejected()) 1316 1317 // given 1318 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1319 1320 // when 1321 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1322 1323 // then 1324 assert.Empty(t, toBeEnacted) 1325 } 1326 1327 func testComputingGovernanceStateHashIsDeterministic(t *testing.T) { 1328 eng := getTestEngine(t, time.Now()) 1329 1330 require.Equal(t, 1331 "a1292c11ccdb876535c6699e8217e1a1294190d83e4233ecc490d32df17a4116", 1332 hex.EncodeToString(eng.Hash()), 1333 "hash is not deterministic", 1334 ) 1335 1336 // when 1337 proposer := vgrand.RandomStr(5) 1338 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 1339 proposal := eng.newProposalForNewMarket(proposer, now, nil, nil, true) 1340 1341 // setup 1342 eng.ensureTokenBalanceForParty(t, proposer, 1) 1343 eng.ensureStakingAssetTotalSupply(t, 9) 1344 eng.ensureAllAssetEnabled(t) 1345 1346 // expect 1347 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1348 1349 // when 1350 _, err := eng.submitProposal(t, proposal) 1351 1352 // then 1353 require.NoError(t, err) 1354 1355 // given 1356 voter1 := vgrand.RandomStr(5) 1357 1358 // setup 1359 eng.ensureTokenBalanceForParty(t, voter1, 7) 1360 1361 // expect 1362 eng.expectVoteEvent(t, voter1, proposal.ID) 1363 1364 // then 1365 err = eng.addYesVote(t, voter1, proposal.ID) 1366 1367 // then 1368 require.NoError(t, err) 1369 // test hash before enactment 1370 require.Equal(t, 1371 "d43f721a8e28c5bad0e78ab7052b8990be753044bb355056519fab76e8de50a7", 1372 hex.EncodeToString(eng.Hash()), 1373 "hash is not deterministic", 1374 ) 1375 1376 // given 1377 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1378 1379 // setup 1380 eng.ensureTokenBalanceForParty(t, voter1, 7) 1381 1382 // expect 1383 eng.expectPassedProposalEvent(t, proposal.ID) 1384 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7") 1385 1386 // when 1387 eng.expectGetMarketState(t, proposal.ID) 1388 eng.OnTick(context.Background(), afterClosing) 1389 1390 // given 1391 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1392 1393 // when 1394 // no calculations, no state change, simply removed from governance engine 1395 eng.expectGetMarketState(t, proposal.ID) 1396 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1397 1398 // then 1399 require.Len(t, toBeEnacted, 1) 1400 require.Equal(t, 1401 "fbf86f159b135501153cda0fc333751df764290a3ae61c3f45f19f9c19445563", 1402 hex.EncodeToString(eng.Hash()), 1403 "hash is not deterministic", 1404 ) 1405 } 1406 1407 func getTestEngine(t *testing.T, now time.Time) *tstEngine { 1408 t.Helper() 1409 1410 cfg := governance.NewDefaultConfig() 1411 log := logging.NewTestLogger() 1412 1413 ctrl := gomock.NewController(t) 1414 accounts := mocks.NewMockStakingAccounts(ctrl) 1415 markets := mocks.NewMockMarkets(ctrl) 1416 assets := mocks.NewMockAssets(ctrl) 1417 ts := mocks.NewMockTimeService(ctrl) 1418 broker := bmocks.NewMockBroker(ctrl) 1419 witness := mocks.NewMockWitness(ctrl) 1420 banking := mocks.NewMockBanking(ctrl) 1421 1422 // Set default network parameters 1423 netp := netparams.New(log, netparams.NewDefaultConfig(), broker) 1424 1425 ctx := context.Background() 1426 1427 ts.EXPECT().GetTimeNow().Return(now).AnyTimes() 1428 1429 broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalMarketMinVoterBalance, "1")).Times(1) 1430 require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalMarketMinVoterBalance, "1")) 1431 1432 broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalMarketRequiredParticipation, "0.5")).Times(1) 1433 require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalMarketRequiredParticipation, "0.5")) 1434 1435 broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1")).Times(1) 1436 require.NoError(t, netp.Update(ctx, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1")) 1437 1438 // Initialise engine as validator 1439 eng := governance.NewEngine(log, cfg, accounts, ts, broker, assets, witness, markets, netp, banking) 1440 require.NotNil(t, eng) 1441 1442 tEng := &tstEngine{ 1443 Engine: eng, 1444 ctrl: ctrl, 1445 accounts: accounts, 1446 markets: markets, 1447 tsvc: ts, 1448 broker: broker, 1449 assets: assets, 1450 witness: witness, 1451 netp: netp, 1452 tokenBal: map[string]uint64{}, 1453 els: map[string]map[string]float64{}, 1454 } 1455 // ensure the balance is always returned as expected 1456 1457 broker.EXPECT().Send(gomock.Any()).Times(1) 1458 tEng.netp.Update(context.Background(), netparams.BlockchainsPrimaryEthereumConfig, "{\"network_id\":\"1\",\"chain_id\":\"1\",\"collateral_bridge_contract\":{\"address\":\"0x23872549cE10B40e31D6577e0A920088B0E0666a\"},\"confirmations\":64,\"staking_bridge_contract\":{\"address\":\"0x195064D33f09e0c42cF98E665D9506e0dC17de68\",\"deployment_block_height\":13146644},\"token_vesting_contract\":{\"address\":\"0x23d1bFE8fA50a167816fBD79D7932577c06011f4\",\"deployment_block_height\":12834524},\"multisig_control_contract\":{\"address\":\"0xDD2df0E7583ff2acfed5e49Df4a424129cA9B58F\",\"deployment_block_height\":15263593}}") 1459 tEng.accounts.EXPECT().GetAvailableBalance(gomock.Any()).AnyTimes().DoAndReturn(func(p string) (*num.Uint, error) { 1460 b, ok := tEng.tokenBal[p] 1461 if !ok { 1462 return nil, errNoBalanceForParty 1463 } 1464 return num.NewUint(b), nil 1465 }) 1466 return tEng 1467 } 1468 1469 func newFreeformTerms() *types.ProposalTermsNewFreeform { 1470 return &types.ProposalTermsNewFreeform{ 1471 NewFreeform: &types.NewFreeform{}, 1472 } 1473 } 1474 1475 func newAssetTerms() *types.ProposalTermsNewAsset { 1476 return &types.ProposalTermsNewAsset{ 1477 NewAsset: &types.NewAsset{ 1478 Changes: &types.AssetDetails{ 1479 Name: "token", 1480 Symbol: "TKN", 1481 Decimals: 18, 1482 Quantum: num.DecimalFromFloat(1), 1483 Source: &types.AssetDetailsBuiltinAsset{ 1484 BuiltinAsset: &types.BuiltinAsset{ 1485 MaxFaucetAmountMint: num.NewUint(1), 1486 }, 1487 }, 1488 }, 1489 }, 1490 } 1491 } 1492 1493 func produceNonTimeTriggeredDataSourceSpec() (*dstypes.SpecFilter, *datasource.SpecBindingForFuture) { 1494 return &dstypes.SpecFilter{ 1495 Key: &dstypes.SpecPropertyKey{ 1496 Name: "trading.terminated", 1497 Type: datapb.PropertyKey_TYPE_BOOLEAN, 1498 }, 1499 Conditions: []*dstypes.SpecCondition{}, 1500 }, 1501 &datasource.SpecBindingForFuture{ 1502 SettlementDataProperty: "prices.ETH.value", 1503 TradingTerminationProperty: "trading.terminated", 1504 } 1505 } 1506 1507 func produceTimeTriggeredDataSourceSpec(termTimestamp time.Time) (*dstypes.SpecFilter, *datasource.SpecBindingForFuture) { 1508 return &dstypes.SpecFilter{ 1509 Key: &dstypes.SpecPropertyKey{ 1510 Name: "vegaprotocol.builtin.timestamp", 1511 Type: datapb.PropertyKey_TYPE_TIMESTAMP, 1512 }, 1513 Conditions: []*dstypes.SpecCondition{ 1514 { 1515 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1516 Value: strconv.FormatInt(termTimestamp.Unix(), 10), 1517 }, 1518 }, 1519 }, 1520 &datasource.SpecBindingForFuture{ 1521 SettlementDataProperty: "prices.ETH.value", 1522 TradingTerminationProperty: "vegaprotocol.builtin.timestamp", 1523 } 1524 } 1525 1526 func newNetParamTerms(key, value string) *types.ProposalTermsUpdateNetworkParameter { 1527 return &types.ProposalTermsUpdateNetworkParameter{ 1528 UpdateNetworkParameter: &types.UpdateNetworkParameter{ 1529 Changes: &types.NetworkParameter{ 1530 Key: key, 1531 Value: value, 1532 }, 1533 }, 1534 } 1535 } 1536 1537 func newMarketTerms(termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool, successor *types.SuccessorConfig, fCap *types.FutureCap) *types.ProposalTermsNewMarket { 1538 var dt *dsdefinition.Definition 1539 if termExt { 1540 if termFilter == nil { 1541 termFilter, termBinding = produceNonTimeTriggeredDataSourceSpec() 1542 } 1543 1544 dt = datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig( 1545 &signedoracle.SpecConfiguration{ 1546 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 1547 Filters: []*dstypes.SpecFilter{ 1548 termFilter, 1549 }, 1550 }, 1551 ) 1552 } else { 1553 tm := time.Now().Add(time.Hour * 24 * 365) 1554 if termFilter == nil { 1555 _, termBinding = produceTimeTriggeredDataSourceSpec(tm) 1556 } 1557 1558 dt = datasource.NewDefinition(datasource.ContentTypeInternalTimeTermination).SetTimeTriggerConditionConfig( 1559 []*dstypes.SpecCondition{ 1560 { 1561 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1562 Value: fmt.Sprintf("%d", tm.UnixNano()), 1563 }, 1564 }, 1565 ) 1566 } 1567 1568 return &types.ProposalTermsNewMarket{ 1569 NewMarket: &types.NewMarket{ 1570 Changes: &types.NewMarketConfiguration{ 1571 Instrument: &types.InstrumentConfiguration{ 1572 Name: "June 2020 GBP vs VUSD future", 1573 Code: "CRYPTO:GBPVUSD/JUN20", 1574 Product: &types.InstrumentConfigurationFuture{ 1575 Future: &types.FutureProduct{ 1576 SettlementAsset: "VUSD", 1577 QuoteName: "VUSD", 1578 DataSourceSpecForSettlementData: *datasource.NewDefinition( 1579 datasource.ContentTypeOracle, 1580 ).SetOracleConfig( 1581 &signedoracle.SpecConfiguration{ 1582 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 1583 Filters: []*dstypes.SpecFilter{ 1584 { 1585 Key: &dstypes.SpecPropertyKey{ 1586 Name: "prices.ETH.value", 1587 Type: datapb.PropertyKey_TYPE_INTEGER, 1588 }, 1589 Conditions: []*dstypes.SpecCondition{ 1590 { 1591 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1592 Value: "0", 1593 }, 1594 }, 1595 }, 1596 }, 1597 }, 1598 ), 1599 DataSourceSpecForTradingTermination: *dt, 1600 DataSourceSpecBinding: termBinding, 1601 Cap: fCap, 1602 }, 1603 }, 1604 }, 1605 RiskParameters: &types.NewMarketConfigurationLogNormal{ 1606 LogNormal: &types.LogNormalRiskModel{ 1607 RiskAversionParameter: num.DecimalFromFloat(0.01), 1608 Tau: num.DecimalFromFloat(0.00011407711613050422), 1609 Params: &types.LogNormalModelParams{ 1610 Mu: num.DecimalZero(), 1611 R: num.DecimalFromFloat(0.016), 1612 Sigma: num.DecimalFromFloat(0.09), 1613 }, 1614 }, 1615 }, 1616 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1617 DecimalPlaces: 0, 1618 LiquiditySLAParameters: &types.LiquiditySLAParams{ 1619 PriceRange: num.DecimalFromFloat(0.95), 1620 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 1621 PerformanceHysteresisEpochs: 4, 1622 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 1623 }, 1624 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1625 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1626 Successor: successor, 1627 LiquidationStrategy: &types.LiquidationStrategy{ 1628 DisposalTimeStep: 10 * time.Second, 1629 DisposalFraction: num.DecimalFromFloat(0.1), 1630 FullDisposalSize: 20, 1631 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1632 DisposalSlippage: num.DecimalFromFloat(0.1), 1633 }, 1634 TickSize: num.NewUint(1), 1635 }, 1636 }, 1637 } 1638 } 1639 1640 //nolint:unparam 1641 func newPerpsMarketTerms(termFilter *dstypes.SpecFilter, binding *datasource.SpecBindingForPerps) *types.ProposalTermsNewMarket { 1642 if binding == nil { 1643 binding = &datasource.SpecBindingForPerps{ 1644 SettlementDataProperty: "price.ETH.value", 1645 SettlementScheduleProperty: "vegaprotocol.builtin.timetrigger", 1646 } 1647 } 1648 1649 return &types.ProposalTermsNewMarket{ 1650 NewMarket: &types.NewMarket{ 1651 Changes: &types.NewMarketConfiguration{ 1652 Instrument: &types.InstrumentConfiguration{ 1653 Name: "GBP/USDT PERPS", 1654 Code: "CRYPTO:GBP/USD", 1655 Product: &types.InstrumentConfigurationPerps{ 1656 Perps: &types.PerpsProduct{ 1657 SettlementAsset: "USDT", 1658 QuoteName: "USD", 1659 DataSourceSpecForSettlementData: *datasource.NewDefinition( 1660 datasource.ContentTypeOracle, 1661 ).SetOracleConfig( 1662 &signedoracle.SpecConfiguration{ 1663 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 1664 Filters: []*dstypes.SpecFilter{ 1665 { 1666 Key: &dstypes.SpecPropertyKey{ 1667 Name: "price.ETH.value", 1668 Type: datapb.PropertyKey_TYPE_INTEGER, 1669 }, 1670 Conditions: []*dstypes.SpecCondition{ 1671 { 1672 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1673 Value: "0", 1674 }, 1675 }, 1676 }, 1677 }, 1678 }, 1679 ), 1680 DataSourceSpecForSettlementSchedule: *datasource.NewDefinition(datasource.ContentTypeInternalTimeTriggerTermination).SetTimeTriggerTriggersConfig( 1681 common.InternalTimeTriggers{ 1682 { 1683 Initial: nil, 1684 Every: 300, 1685 }, 1686 }, 1687 ).SetTimeTriggerConditionConfig([]*dstypes.SpecCondition{ 1688 { 1689 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1690 Value: "0", 1691 }, 1692 }), 1693 DataSourceSpecBinding: binding, 1694 }, 1695 }, 1696 }, 1697 RiskParameters: &types.NewMarketConfigurationLogNormal{ 1698 LogNormal: &types.LogNormalRiskModel{ 1699 RiskAversionParameter: num.DecimalFromFloat(0.01), 1700 Tau: num.DecimalFromFloat(0.00011407711613050422), 1701 Params: &types.LogNormalModelParams{ 1702 Mu: num.DecimalZero(), 1703 R: num.DecimalFromFloat(0.016), 1704 Sigma: num.DecimalFromFloat(0.09), 1705 }, 1706 }, 1707 }, 1708 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1709 DecimalPlaces: 0, 1710 LiquiditySLAParameters: &types.LiquiditySLAParams{ 1711 PriceRange: num.DecimalFromFloat(0.95), 1712 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 1713 PerformanceHysteresisEpochs: 4, 1714 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 1715 }, 1716 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1717 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1718 LiquidationStrategy: &types.LiquidationStrategy{ 1719 DisposalTimeStep: 10 * time.Second, 1720 DisposalFraction: num.DecimalFromFloat(0.1), 1721 FullDisposalSize: 20, 1722 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1723 DisposalSlippage: num.DecimalFromFloat(0.1), 1724 }, 1725 TickSize: num.UintOne(), 1726 }, 1727 }, 1728 } 1729 } 1730 1731 func newUpdateMarketState(tp types.MarketStateUpdateType, marketID string, price *num.Uint) *types.ProposalTermsUpdateMarketState { 1732 return &types.ProposalTermsUpdateMarketState{ 1733 UpdateMarketState: &types.UpdateMarketState{ 1734 Changes: &types.MarketStateUpdateConfiguration{ 1735 MarketID: marketID, 1736 SettlementPrice: price, 1737 UpdateType: tp, 1738 }, 1739 }, 1740 } 1741 } 1742 1743 func newSpotMarketTerms() *types.ProposalTermsNewSpotMarket { 1744 return &types.ProposalTermsNewSpotMarket{ 1745 NewSpotMarket: &types.NewSpotMarket{ 1746 Changes: &types.NewSpotMarketConfiguration{ 1747 Instrument: &types.InstrumentConfiguration{ 1748 Name: "BTC/USDT Spot", 1749 Code: "CRYPTO:BTCUSDT", 1750 Product: &types.InstrumentConfigurationSpot{ 1751 Spot: &types.SpotProduct{ 1752 Name: "BTC/USDT", 1753 BaseAsset: "BTC", 1754 QuoteAsset: "USDT", 1755 }, 1756 }, 1757 }, 1758 RiskParameters: &types.NewSpotMarketConfigurationLogNormal{ 1759 LogNormal: &types.LogNormalRiskModel{ 1760 RiskAversionParameter: num.DecimalFromFloat(0.01), 1761 Tau: num.DecimalFromFloat(0.00011407711613050422), 1762 Params: &types.LogNormalModelParams{ 1763 Mu: num.DecimalZero(), 1764 R: num.DecimalFromFloat(0.016), 1765 Sigma: num.DecimalFromFloat(0.09), 1766 }, 1767 }, 1768 }, 1769 Metadata: []string{"asset_class:spot/crypto", "product:spot"}, 1770 PriceDecimalPlaces: 0, 1771 SLAParams: &types.LiquiditySLAParams{ 1772 PriceRange: num.DecimalOne(), 1773 CommitmentMinTimeFraction: num.DecimalFromFloat(0.5), 1774 SlaCompetitionFactor: num.DecimalOne(), 1775 PerformanceHysteresisEpochs: 1, 1776 }, 1777 TickSize: num.UintOne(), 1778 }, 1779 }, 1780 } 1781 } 1782 1783 func updateSpotMarketTerms() *types.ProposalTermsUpdateSpotMarket { 1784 return &types.ProposalTermsUpdateSpotMarket{ 1785 UpdateSpotMarket: &types.UpdateSpotMarket{ 1786 MarketID: vgrand.RandomStr(5), 1787 Changes: &types.UpdateSpotMarketConfiguration{ 1788 Instrument: &types.InstrumentConfiguration{ 1789 Name: "some name", 1790 Code: "some code", 1791 }, 1792 RiskParameters: &types.UpdateSpotMarketConfigurationLogNormal{ 1793 LogNormal: &types.LogNormalRiskModel{ 1794 RiskAversionParameter: num.DecimalFromFloat(0.02), 1795 Tau: num.DecimalFromFloat(0.0002), 1796 Params: &types.LogNormalModelParams{ 1797 Mu: num.DecimalZero(), 1798 R: num.DecimalFromFloat(0.015), 1799 Sigma: num.DecimalFromFloat(0.08), 1800 }, 1801 }, 1802 }, 1803 Metadata: []string{"asset_class:spot/crypto", "product:spot"}, 1804 SLAParams: &types.LiquiditySLAParams{ 1805 PriceRange: num.DecimalOne(), 1806 CommitmentMinTimeFraction: num.DecimalFromFloat(0.2), 1807 SlaCompetitionFactor: num.DecimalFromFloat(0.23), 1808 PerformanceHysteresisEpochs: 2, 1809 }, 1810 TargetStakeParameters: &types.TargetStakeParameters{ 1811 TimeWindow: 1, 1812 ScalingFactor: num.DecimalE(), 1813 }, 1814 TickSize: num.UintOne(), 1815 }, 1816 }, 1817 } 1818 } 1819 1820 func updateMarketTerms(termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool) *types.ProposalTermsUpdateMarket { 1821 var dt *dsdefinition.Definition 1822 if termExt { 1823 if termFilter == nil { 1824 termFilter = &dstypes.SpecFilter{ 1825 Key: &dstypes.SpecPropertyKey{ 1826 Name: "trading.terminated", 1827 Type: datapb.PropertyKey_TYPE_BOOLEAN, 1828 }, 1829 Conditions: []*dstypes.SpecCondition{}, 1830 } 1831 1832 termBinding = &datasource.SpecBindingForFuture{ 1833 SettlementDataProperty: "prices.ETH.value", 1834 TradingTerminationProperty: "trading.terminated", 1835 } 1836 } 1837 dt = datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig( 1838 &signedoracle.SpecConfiguration{ 1839 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 1840 Filters: []*dstypes.SpecFilter{ 1841 termFilter, 1842 }, 1843 }, 1844 ) 1845 } else { 1846 tm := time.Now().Add(time.Hour * 24 * 365) 1847 if termFilter == nil { 1848 _, termBinding = produceTimeTriggeredDataSourceSpec(tm) 1849 } 1850 1851 dt = datasource.NewDefinition(datasource.ContentTypeInternalTimeTermination).SetTimeTriggerConditionConfig( 1852 []*dstypes.SpecCondition{ 1853 { 1854 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1855 Value: fmt.Sprintf("%d", tm.UnixNano()), 1856 }, 1857 }, 1858 ) 1859 } 1860 1861 return &types.ProposalTermsUpdateMarket{ 1862 UpdateMarket: &types.UpdateMarket{ 1863 MarketID: vgrand.RandomStr(5), 1864 Changes: &types.UpdateMarketConfiguration{ 1865 Instrument: &types.UpdateInstrumentConfiguration{ 1866 Code: "CRYPTO:GBPVUSD/JUN20", 1867 Name: "UPDATED_MARKET_NAME", 1868 Product: &types.UpdateInstrumentConfigurationFuture{ 1869 Future: &types.UpdateFutureProduct{ 1870 QuoteName: "VUSD", 1871 DataSourceSpecForSettlementData: *datasource.NewDefinition( 1872 datasource.ContentTypeOracle, 1873 ).SetOracleConfig( 1874 &signedoracle.SpecConfiguration{ 1875 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 1876 Filters: []*dstypes.SpecFilter{ 1877 { 1878 Key: &dstypes.SpecPropertyKey{ 1879 Name: "prices.ETH.value", 1880 Type: datapb.PropertyKey_TYPE_INTEGER, 1881 }, 1882 Conditions: []*dstypes.SpecCondition{}, 1883 }, 1884 }, 1885 }, 1886 ), 1887 DataSourceSpecForTradingTermination: *dt, 1888 DataSourceSpecBinding: termBinding, 1889 }, 1890 }, 1891 }, 1892 RiskParameters: &types.UpdateMarketConfigurationLogNormal{ 1893 LogNormal: &types.LogNormalRiskModel{ 1894 RiskAversionParameter: num.DecimalFromFloat(0.01), 1895 Tau: num.DecimalFromFloat(0.00011407711613050422), 1896 Params: &types.LogNormalModelParams{ 1897 Mu: num.DecimalZero(), 1898 R: num.DecimalFromFloat(0.016), 1899 Sigma: num.DecimalFromFloat(0.09), 1900 }, 1901 }, 1902 }, 1903 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1904 LiquiditySLAParameters: &types.LiquiditySLAParams{ 1905 PriceRange: num.DecimalFromFloat(0.95), 1906 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 1907 PerformanceHysteresisEpochs: 4, 1908 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 1909 }, 1910 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1911 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1912 LiquidationStrategy: &types.LiquidationStrategy{ 1913 DisposalTimeStep: 10 * time.Second, 1914 DisposalFraction: num.DecimalFromFloat(0.1), 1915 FullDisposalSize: 20, 1916 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1917 DisposalSlippage: num.DecimalFromFloat(0.1), 1918 }, 1919 TickSize: num.UintOne(), 1920 }, 1921 }, 1922 } 1923 } 1924 1925 func (e *tstEngine) submitProposal(t *testing.T, proposal types.Proposal) (*governance.ToSubmit, error) { 1926 t.Helper() 1927 return e.SubmitProposal( 1928 context.Background(), 1929 *types.ProposalSubmissionFromProposal(&proposal), 1930 proposal.ID, 1931 proposal.Party, 1932 ) 1933 } 1934 1935 func (e *tstEngine) submitBatchProposal( 1936 t *testing.T, 1937 submission types.BatchProposalSubmission, 1938 proposalID, party string, 1939 ) ([]*governance.ToSubmit, error) { 1940 t.Helper() 1941 return e.SubmitBatchProposal( 1942 context.Background(), 1943 submission, 1944 proposalID, 1945 party, 1946 ) 1947 } 1948 1949 func (e *tstEngine) addYesVote(t *testing.T, party, proposal string) error { 1950 t.Helper() 1951 return e.AddVote(context.Background(), types.VoteSubmission{ 1952 ProposalID: proposal, 1953 Value: types.VoteValueYes, 1954 }, party) 1955 } 1956 1957 func (e *tstEngine) addNoVote(t *testing.T, party, proposal string) error { 1958 t.Helper() 1959 return e.AddVote(context.Background(), types.VoteSubmission{ 1960 ProposalID: proposal, 1961 Value: types.VoteValueNo, 1962 }, party) 1963 } 1964 1965 func (e *tstEngine) newValidPartyTimes(partyID string, balance uint64, _ int) *types.Party { 1966 // is called with 0 times, which messes up the expected calls when adding votes 1967 e.tokenBal[partyID] = balance 1968 return &types.Party{Id: partyID} 1969 } 1970 1971 func (e *tstEngine) newValidParty(partyID string, balance uint64) *types.Party { 1972 return e.newValidPartyTimes(partyID, balance, 1) 1973 } 1974 1975 func (e *tstEngine) newProposalID() string { 1976 e.proposalCounter++ 1977 return fmt.Sprintf("proposal-id-%d", e.proposalCounter) 1978 } 1979 1980 func (e *tstEngine) newBatchSubmission( 1981 closingTimestamp int64, 1982 proposals ...types.Proposal, 1983 ) types.BatchProposalSubmission { 1984 id := e.newProposalID() 1985 1986 sub := types.BatchProposalSubmission{ 1987 Reference: id, 1988 Terms: &types.BatchProposalTerms{ 1989 ClosingTimestamp: closingTimestamp, 1990 }, 1991 Rationale: &types.ProposalRationale{ 1992 Description: "some description", 1993 }, 1994 } 1995 1996 for _, proposal := range proposals { 1997 sub.Terms.Changes = append(sub.Terms.Changes, types.BatchProposalChange{ 1998 ID: proposal.ID, 1999 Change: proposal.Terms.Change, 2000 EnactmentTime: proposal.Terms.EnactmentTimestamp, 2001 ValidationTime: proposal.Terms.ValidationTimestamp, 2002 }) 2003 } 2004 2005 return sub 2006 } 2007 2008 //nolint:unparam 2009 func (e *tstEngine) newProposalForNewPerpsMarket( 2010 partyID string, 2011 now time.Time, 2012 termFilter *dstypes.SpecFilter, 2013 termBinding *datasource.SpecBindingForPerps, 2014 termExt bool, 2015 ) types.Proposal { 2016 id := e.newProposalID() 2017 return types.Proposal{ 2018 ID: id, 2019 Reference: "ref-" + id, 2020 Party: partyID, 2021 State: types.ProposalStateOpen, 2022 Terms: &types.ProposalTerms{ 2023 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2024 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2025 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2026 Change: newPerpsMarketTerms(termFilter, termBinding), 2027 }, 2028 Rationale: &types.ProposalRationale{ 2029 Description: "some description", 2030 }, 2031 } 2032 } 2033 2034 func (e *tstEngine) newProposalForCapped( 2035 partyID string, 2036 now time.Time, 2037 _ *dstypes.SpecFilter, 2038 _ *datasource.SpecBindingForFuture, 2039 _ bool, 2040 fCap *types.FutureCap, 2041 ) types.Proposal { 2042 return e.getMarketProposal(partyID, now, nil, nil, true, fCap) 2043 } 2044 2045 func (e *tstEngine) newProposalForNewMarket( 2046 partyID string, 2047 now time.Time, 2048 termFilter *dstypes.SpecFilter, 2049 termBinding *datasource.SpecBindingForFuture, 2050 termExt bool, 2051 ) types.Proposal { 2052 return e.getMarketProposal(partyID, now, termFilter, termBinding, termExt, nil) 2053 } 2054 2055 func (e *tstEngine) getMarketProposal( 2056 partyID string, 2057 now time.Time, 2058 termFilter *dstypes.SpecFilter, 2059 termBinding *datasource.SpecBindingForFuture, 2060 termExt bool, 2061 fCap *types.FutureCap, 2062 ) types.Proposal { 2063 id := e.newProposalID() 2064 return types.Proposal{ 2065 ID: id, 2066 Reference: "ref-" + id, 2067 Party: partyID, 2068 State: types.ProposalStateOpen, 2069 Terms: &types.ProposalTerms{ 2070 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2071 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2072 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2073 Change: newMarketTerms(termFilter, termBinding, termExt, nil, fCap), 2074 }, 2075 Rationale: &types.ProposalRationale{ 2076 Description: "some description", 2077 }, 2078 } 2079 } 2080 2081 func (e *tstEngine) newProposalForSuccessorMarket(partyID string, now time.Time, termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool, successor *types.SuccessorConfig) types.Proposal { 2082 id := e.newProposalID() 2083 return types.Proposal{ 2084 ID: id, 2085 Reference: "ref-" + id, 2086 Party: partyID, 2087 State: types.ProposalStateOpen, 2088 Terms: &types.ProposalTerms{ 2089 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2090 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2091 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2092 Change: newMarketTerms(termFilter, termBinding, termExt, successor, nil), 2093 }, 2094 Rationale: &types.ProposalRationale{ 2095 Description: "some description", 2096 }, 2097 } 2098 } 2099 2100 func (e *tstEngine) newProposalForUpdateMarketState(partyID string, now time.Time, updateType types.MarketStateUpdateType, price *num.Uint) types.Proposal { 2101 id := e.newProposalID() 2102 return types.Proposal{ 2103 ID: id, 2104 Reference: "ref-" + id, 2105 Party: partyID, 2106 State: types.ProposalStateOpen, 2107 Terms: &types.ProposalTerms{ 2108 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2109 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2110 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2111 Change: newUpdateMarketState(updateType, vgrand.RandomStr(5), price), 2112 }, 2113 Rationale: &types.ProposalRationale{ 2114 Description: "some description", 2115 }, 2116 } 2117 } 2118 2119 func (e *tstEngine) newProposalForNewSpotMarket(partyID string, now time.Time) types.Proposal { 2120 id := e.newProposalID() 2121 return types.Proposal{ 2122 ID: id, 2123 Reference: "ref-" + id, 2124 Party: partyID, 2125 State: types.ProposalStateOpen, 2126 Terms: &types.ProposalTerms{ 2127 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2128 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2129 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2130 Change: newSpotMarketTerms(), 2131 }, 2132 Rationale: &types.ProposalRationale{ 2133 Description: "some description", 2134 }, 2135 } 2136 } 2137 2138 func (e *tstEngine) newProposalForSpotMarketUpdate(marketID, partyID string, now time.Time) types.Proposal { 2139 id := e.newProposalID() 2140 prop := types.Proposal{ 2141 ID: id, 2142 Reference: "ref-" + id, 2143 Party: partyID, 2144 State: types.ProposalStateOpen, 2145 Terms: &types.ProposalTerms{ 2146 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2147 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2148 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2149 Change: updateSpotMarketTerms(), 2150 }, 2151 Rationale: &types.ProposalRationale{ 2152 Description: "some description", 2153 }, 2154 } 2155 switch p := prop.Terms.Change.(type) { 2156 case *types.ProposalTermsUpdateSpotMarket: 2157 p.UpdateSpotMarket.MarketID = marketID 2158 } 2159 return prop 2160 } 2161 2162 func (e *tstEngine) newProposalForMarketUpdate(marketID, partyID string, now time.Time, termFilter *dstypes.SpecFilter, termBinding *datasource.SpecBindingForFuture, termExt bool) types.Proposal { 2163 id := e.newProposalID() 2164 prop := types.Proposal{ 2165 ID: id, 2166 Reference: "ref-" + id, 2167 Party: partyID, 2168 State: types.ProposalStateOpen, 2169 Terms: &types.ProposalTerms{ 2170 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2171 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2172 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 2173 Change: updateMarketTerms(termFilter, termBinding, termExt), 2174 }, 2175 Rationale: &types.ProposalRationale{ 2176 Description: "some description", 2177 }, 2178 } 2179 switch p := prop.Terms.Change.(type) { 2180 case *types.ProposalTermsUpdateMarket: 2181 p.UpdateMarket.MarketID = marketID 2182 } 2183 return prop 2184 } 2185 2186 func (e *tstEngine) newProposalForReferralProgramUpdate(partyID string, now time.Time, configuration *types.ReferralProgramChanges) types.Proposal { 2187 id := e.newProposalID() 2188 prop := types.Proposal{ 2189 ID: id, 2190 Reference: "ref-" + id, 2191 Party: partyID, 2192 State: types.ProposalStateOpen, 2193 Terms: &types.ProposalTerms{ 2194 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2195 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2196 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 2197 Change: &types.ProposalTermsUpdateReferralProgram{ 2198 UpdateReferralProgram: &types.UpdateReferralProgram{ 2199 Changes: configuration, 2200 }, 2201 }, 2202 }, 2203 Rationale: &types.ProposalRationale{ 2204 Description: "some description", 2205 }, 2206 } 2207 return prop 2208 } 2209 2210 func (e *tstEngine) newProposalForVolumeRebateProgramUpdate(partyID string, now time.Time, configuration *types.VolumeRebateProgramChanges) types.Proposal { 2211 id := e.newProposalID() 2212 prop := types.Proposal{ 2213 ID: id, 2214 Reference: "ref-" + id, 2215 Party: partyID, 2216 State: types.ProposalStateOpen, 2217 Terms: &types.ProposalTerms{ 2218 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2219 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2220 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 2221 Change: &types.ProposalTermsUpdateVolumeRebateProgram{ 2222 UpdateVolumeRebateProgram: &types.UpdateVolumeRebateProgram{ 2223 Changes: configuration, 2224 }, 2225 }, 2226 }, 2227 Rationale: &types.ProposalRationale{ 2228 Description: "some description", 2229 }, 2230 } 2231 return prop 2232 } 2233 2234 func (e *tstEngine) newProposalForNewProtocolAutomatedPurchase(partyID string, now time.Time, configuration *types.NewProtocolAutomatedPurchaseChanges) types.Proposal { 2235 id := e.newProposalID() 2236 prop := types.Proposal{ 2237 ID: id, 2238 Reference: "ref-" + id, 2239 Party: partyID, 2240 State: types.ProposalStateOpen, 2241 Terms: &types.ProposalTerms{ 2242 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2243 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2244 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 2245 Change: &types.ProposalTermsNewProtocolAutomatedPurchase{ 2246 NewProtocolAutomatedPurchase: &types.NewProtocolAutomatedPurchase{ 2247 Changes: configuration, 2248 }, 2249 }, 2250 }, 2251 Rationale: &types.ProposalRationale{ 2252 Description: "some description", 2253 }, 2254 } 2255 return prop 2256 } 2257 2258 func (e *tstEngine) newProposalForVolumeDiscountProgramUpdate(partyID string, now time.Time, configuration *types.VolumeDiscountProgramChanges) types.Proposal { 2259 id := e.newProposalID() 2260 prop := types.Proposal{ 2261 ID: id, 2262 Reference: "ref-" + id, 2263 Party: partyID, 2264 State: types.ProposalStateOpen, 2265 Terms: &types.ProposalTerms{ 2266 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 2267 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 2268 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 2269 Change: &types.ProposalTermsUpdateVolumeDiscountProgram{ 2270 UpdateVolumeDiscountProgram: &types.UpdateVolumeDiscountProgram{ 2271 Changes: configuration, 2272 }, 2273 }, 2274 }, 2275 Rationale: &types.ProposalRationale{ 2276 Description: "some description", 2277 }, 2278 } 2279 return prop 2280 } 2281 2282 func (e *tstEngine) newProposalForNewAsset(partyID string, now time.Time) types.Proposal { 2283 id := e.newProposalID() 2284 return types.Proposal{ 2285 ID: id, 2286 Reference: "ref-" + id, 2287 Party: partyID, 2288 State: types.ProposalStateOpen, 2289 Terms: &types.ProposalTerms{ 2290 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2291 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 2292 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2293 Change: newAssetTerms(), 2294 }, 2295 Rationale: &types.ProposalRationale{ 2296 Description: "some description", 2297 }, 2298 } 2299 } 2300 2301 func (e *tstEngine) newFreeformProposal(partyID string, now time.Time) types.Proposal { 2302 id := e.newProposalID() 2303 return types.Proposal{ 2304 ID: id, 2305 Reference: "ref-" + id, 2306 Party: partyID, 2307 State: types.ProposalStateOpen, 2308 Terms: &types.ProposalTerms{ 2309 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 2310 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 2311 Change: newFreeformTerms(), 2312 }, 2313 Rationale: &types.ProposalRationale{ 2314 Title: "https://example.com", 2315 Description: "Test my freeform proposal", 2316 }, 2317 } 2318 } 2319 2320 func (e *tstEngine) expectTotalGovernanceTokenFromVoteEvents(t *testing.T, weight, balance string) { 2321 t.Helper() 2322 e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) { 2323 v, ok := evts[0].(*events.Vote) 2324 require.True(t, ok) 2325 assert.Equal(t, weight, v.TotalGovernanceTokenWeight()) 2326 assert.Equal(t, balance, v.TotalGovernanceTokenBalance()) 2327 }) 2328 } 2329 2330 func (e *tstEngine) expectVoteEvents(t *testing.T) { 2331 t.Helper() 2332 e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) { 2333 _, ok := evts[0].(*events.Vote) 2334 require.True(t, ok) 2335 }) 2336 } 2337 2338 func (e *tstEngine) expectPassedProposalEvent(t *testing.T, proposal string) { 2339 t.Helper() 2340 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(evt events.Event) { 2341 pe, ok := evt.(*events.Proposal) 2342 require.True(t, ok) 2343 p := pe.Proposal() 2344 assert.Equal(t, types.ProposalStatePassed.String(), p.State.String()) 2345 assert.Equal(t, proposal, p.Id) 2346 }) 2347 } 2348 2349 func (e *tstEngine) expectDeclinedProposalEvent(t *testing.T, proposal string, reason types.ProposalError) { 2350 t.Helper() 2351 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(evt events.Event) { 2352 pe, ok := evt.(*events.Proposal) 2353 require.True(t, ok) 2354 p := pe.Proposal() 2355 assert.Equal(t, types.ProposalStateDeclined.String(), p.State.String()) 2356 assert.Equal(t, proposal, p.Id) 2357 assert.Equal(t, reason.String(), p.Reason.String()) 2358 }) 2359 } 2360 2361 func (e *tstEngine) expectOpenProposalEvent(t *testing.T, party, proposal string) { 2362 t.Helper() 2363 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(ev events.Event) { 2364 pe, ok := ev.(*events.Proposal) 2365 require.True(t, ok) 2366 p := pe.Proposal() 2367 reason := types.ProposalErrorUnspecified 2368 if p.Reason != nil { 2369 reason = *p.Reason 2370 } 2371 errDetails := "" 2372 if p.ErrorDetails != nil { 2373 errDetails = *p.ErrorDetails 2374 } 2375 assert.Equal(t, types.ProposalStateOpen.String(), p.State.String(), fmt.Sprintf("reason: %v, details: %s", reason, errDetails)) 2376 assert.Equal(t, party, p.PartyId) 2377 assert.Equal(t, proposal, p.Id) 2378 }) 2379 } 2380 2381 func (e *tstEngine) expectProposalWaitingForNodeVoteEvent(t *testing.T, party, proposal string) { 2382 t.Helper() 2383 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(ev events.Event) { 2384 pe, ok := ev.(*events.Proposal) 2385 require.True(t, ok) 2386 p := pe.Proposal() 2387 assert.Equal(t, types.ProposalStateWaitingForNodeVote.String(), p.State.String()) 2388 assert.Equal(t, party, p.PartyId) 2389 assert.Equal(t, proposal, p.Id) 2390 }) 2391 } 2392 2393 func (e *tstEngine) expectRejectedProposalEvent(t *testing.T, partyID, proposalID string, reason types.ProposalError) { 2394 t.Helper() 2395 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(e events.Event) { 2396 pe, ok := e.(*events.Proposal) 2397 require.True(t, ok) 2398 p := pe.Proposal() 2399 assert.Equal(t, proposalID, p.Id) 2400 assert.Equal(t, partyID, p.PartyId) 2401 assert.Equal(t, types.ProposalStateRejected.String(), p.State.String()) 2402 assert.Equal(t, reason.String(), p.Reason.String()) 2403 }) 2404 } 2405 2406 type expectedProposal struct { 2407 partyID string 2408 proposalID string 2409 state types.ProposalState 2410 reason types.ProposalError 2411 } 2412 2413 func (e *tstEngine) expectProposalEvents(t *testing.T, expected []expectedProposal) { 2414 t.Helper() 2415 e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) { 2416 assert.GreaterOrEqual(t, len(evts), len(expected)) 2417 for i, expect := range expected { 2418 e := evts[i] 2419 pe, ok := e.(*events.Proposal) 2420 require.True(t, ok) 2421 p := pe.Proposal() 2422 2423 assert.Equal(t, expect.proposalID, p.Id) 2424 assert.Equal(t, expect.partyID, p.PartyId) 2425 assert.Equal(t, expect.state.String(), p.State.String()) 2426 if p.Reason != nil { 2427 assert.Equal(t, expect.reason.String(), p.Reason.String()) 2428 } 2429 } 2430 }) 2431 } 2432 2433 func (e *tstEngine) expectVoteEvent(t *testing.T, party, proposal string) { 2434 t.Helper() 2435 e.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(e events.Event) { 2436 ve, ok := e.(*events.Vote) 2437 require.True(t, ok) 2438 vote := ve.Vote() 2439 assert.Equal(t, proposal, vote.ProposalId) 2440 assert.Equal(t, party, vote.PartyId) 2441 }) 2442 } 2443 2444 func (e *tstEngine) expectRestoredProposals(t *testing.T, proposalIDs []string) { 2445 t.Helper() 2446 e.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(es []events.Event) { 2447 if len(es) == 0 { 2448 t.Errorf("expecting %d proposals to be restored, found %d", len(proposalIDs), len(es)) 2449 } 2450 2451 for i, e := range es { 2452 pe, ok := e.(*events.Proposal) 2453 require.True(t, ok) 2454 assert.Equal(t, proposalIDs[i], pe.ProposalID()) 2455 } 2456 }) 2457 } 2458 2459 func (e *tstEngine) ensureStakingAssetTotalSupply(t *testing.T, supply uint64) { 2460 t.Helper() 2461 e.accounts.EXPECT().GetStakingAssetTotalSupply().Times(1).Return(num.NewUint(supply)) 2462 } 2463 2464 func (e *tstEngine) ensureTokenBalanceForParty(t *testing.T, party string, balance uint64) { 2465 t.Helper() 2466 e.tokenBal[party] = balance 2467 } 2468 2469 func (e *tstEngine) ensureAllAssetEnabled(t *testing.T) { 2470 t.Helper() 2471 details := newAssetTerms() 2472 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(id string) (*assets.Asset, error) { 2473 ret := assets.NewAsset(builtin.New(id, details.NewAsset.Changes)) 2474 return ret, nil 2475 }) 2476 e.assets.EXPECT().IsEnabled(gomock.Any()).AnyTimes().Return(true) 2477 } 2478 2479 func (e *tstEngine) ensureAllAssetEnabledWithDP(t *testing.T, base, quote string, baseDecimals, quoteDecimals uint64) { 2480 t.Helper() 2481 baseDetails := newAssetTerms() 2482 baseDetails.NewAsset.Changes.Symbol = base 2483 baseDetails.NewAsset.Changes.Decimals = baseDecimals 2484 2485 quoteDetails := newAssetTerms() 2486 quoteDetails.NewAsset.Changes.Symbol = quote 2487 quoteDetails.NewAsset.Changes.Decimals = quoteDecimals 2488 2489 e.assets.EXPECT().Get(gomock.Any()).AnyTimes().DoAndReturn(func(id string) (*assets.Asset, error) { 2490 if id == base { 2491 ret := assets.NewAsset(builtin.New(id, baseDetails.NewAsset.Changes)) 2492 return ret, nil 2493 } else { 2494 ret := assets.NewAsset(builtin.New(id, quoteDetails.NewAsset.Changes)) 2495 return ret, nil 2496 } 2497 }) 2498 e.assets.EXPECT().IsEnabled(gomock.Any()).AnyTimes().Return(true) 2499 } 2500 2501 func (e *tstEngine) ensureEquityLikeShareForMarketAndParty(t *testing.T, market, party string, share float64) { 2502 t.Helper() 2503 mels, ok := e.els[market] 2504 if !ok { 2505 mels = map[string]float64{} 2506 } 2507 mels[party] = share 2508 e.els[market] = mels 2509 e.markets.EXPECT(). 2510 GetEquityLikeShareForMarketAndParty(market, party). 2511 AnyTimes(). 2512 Return(num.DecimalFromFloat(share), true) 2513 } 2514 2515 func (e *tstEngine) ensureGetMarket(t *testing.T, marketID string, market types.Market) { 2516 t.Helper() 2517 e.markets.EXPECT(). 2518 GetMarket(marketID, gomock.Any()). 2519 Times(1). 2520 Return(market, true) 2521 } 2522 2523 func (e *tstEngine) ensureGetMarketFuture(t *testing.T, marketID string) { 2524 t.Helper() 2525 e.ensureGetMarket(t, marketID, types.Market{ 2526 TradableInstrument: &types.TradableInstrument{ 2527 Instrument: &types.Instrument{ 2528 Product: &types.InstrumentFuture{ 2529 Future: &types.Future{}, 2530 }, 2531 }, 2532 }, 2533 }, 2534 ) 2535 } 2536 2537 func (e *tstEngine) ensureGetMarketSpot(t *testing.T, marketID string) { 2538 t.Helper() 2539 e.ensureGetMarket(t, marketID, types.Market{ 2540 TradableInstrument: &types.TradableInstrument{ 2541 Instrument: &types.Instrument{ 2542 Product: &types.InstrumentSpot{ 2543 Spot: &types.Spot{}, 2544 }, 2545 }, 2546 }, 2547 }, 2548 ) 2549 } 2550 2551 func (e *tstEngine) ensureGetMarketPerpetual(t *testing.T, marketID string) { 2552 t.Helper() 2553 e.ensureGetMarket(t, marketID, types.Market{ 2554 TradableInstrument: &types.TradableInstrument{ 2555 Instrument: &types.Instrument{ 2556 Product: &types.InstrumentPerps{ 2557 Perps: &types.Perps{}, 2558 }, 2559 }, 2560 }, 2561 }, 2562 ) 2563 } 2564 2565 func (e *tstEngine) expectGetMarketState(t *testing.T, marketID string) { 2566 t.Helper() 2567 e.markets.EXPECT().GetMarketState(marketID).AnyTimes().Return(types.MarketStateActive, nil) 2568 } 2569 2570 func (e *tstEngine) ensureNonExistingMarket(t *testing.T, market string) { 2571 t.Helper() 2572 e.markets.EXPECT().MarketExists(market).Times(1).Return(false) 2573 } 2574 2575 func (e *tstEngine) ensureExistingMarket(t *testing.T, market string) { 2576 t.Helper() 2577 e.markets.EXPECT().MarketExists(market).Times(1).Return(true) 2578 } 2579 2580 func (e *tstEngine) ensureNoAccountForParty(t *testing.T, partyID string) { 2581 t.Helper() 2582 delete(e.tokenBal, partyID) 2583 } 2584 2585 func (e *tstEngine) ensureNetworkParameter(t *testing.T, key, value string) { 2586 t.Helper() 2587 e.broker.EXPECT().Send(gomock.Any()).Times(1) 2588 if err := e.netp.Update(context.Background(), key, value); err != nil { 2589 t.Fatalf("failed to set %s parameter: %v", key, err) 2590 } 2591 }