code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_new_market_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 "fmt" 21 "testing" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/datasource" 25 dstypes "code.vegaprotocol.io/vega/core/datasource/common" 26 dsdefinition "code.vegaprotocol.io/vega/core/datasource/definition" 27 dserrors "code.vegaprotocol.io/vega/core/datasource/errors" 28 "code.vegaprotocol.io/vega/core/datasource/external/signedoracle" 29 "code.vegaprotocol.io/vega/core/events" 30 "code.vegaprotocol.io/vega/core/governance" 31 "code.vegaprotocol.io/vega/core/types" 32 "code.vegaprotocol.io/vega/libs/num" 33 "code.vegaprotocol.io/vega/libs/ptr" 34 vgrand "code.vegaprotocol.io/vega/libs/rand" 35 datapb "code.vegaprotocol.io/vega/protos/vega/data/v1" 36 37 "github.com/golang/mock/gomock" 38 "github.com/pkg/errors" 39 "github.com/stretchr/testify/assert" 40 "github.com/stretchr/testify/require" 41 ) 42 43 func TestProposalForNewMarket(t *testing.T) { 44 t.Run("Submitting a proposal for new market succeeds", testSubmittingProposalForNewMarketSucceeds) 45 t.Run("Submitting a proposal for new capped market succeeds", testSubmittingProposalForNewCappedMarketSucceeds) 46 t.Run("Submitting a proposal for invalid capped market fails", testSubmittingProposalForInvalidCappedMarketFails) 47 t.Run("Submitting a proposal for new perps market succeeds", testSubmittingProposalForNewPerpsMarketSucceeds) 48 t.Run("Submitting a proposal for new perps market succeeds 2", testSubmittingProposalForNewPerpsMarketWithCustomInitialTimeSucceeds) 49 t.Run("Submitting a proposal for new perps market with initial time in past fails", testSubmittingProposalForNewPerpsMarketWithPastInitialTimeFails) 50 t.Run("Submitting a proposal with internal time termination for new market succeeds", testSubmittingProposalWithInternalTimeTerminationForNewMarketSucceeds) 51 t.Run("Submitting a proposal with internal time termination with `less than equal` condition fails", testSubmittingProposalWithInternalTimeTerminationWithLessThanEqualConditionForNewMarketFails) 52 t.Run("Submitting a proposal with internal time settling for new market fails", testSubmittingProposalWithInternalTimeSettlingForNewMarketFails) 53 t.Run("Submitting a proposal with empty settling data for marker market fails", testSubmittingProposalWithEmptySettlingDataForNewMarketFails) 54 t.Run("Submitting a proposal with empty termination data for marker market fails", testSubmittingProposalWithEmptyTerminationDataForNewMarketFails) 55 t.Run("Submitting a proposal with external source using internal time termination key for new market succeeds", testSubmittingProposalWithExternalWithInternalTimeTerminationKeyForNewMarketSucceeds) 56 t.Run("Submitting a proposal with using internal time trigger termination fails", testSubmittingProposalWithInternalTimeTriggerTerminationFails) 57 t.Run("Submitting a proposal with using internal time trigger settlement fails", testSubmittingProposalWithInternalTimeTriggerSettlementFails) 58 t.Run("Submitting a duplicated proposal for new market fails", testSubmittingDuplicatedProposalForNewMarketFails) 59 t.Run("Submitting a duplicated proposal with internal time termination for new market fails", testSubmittingDuplicatedProposalWithInternalTimeTerminationForNewMarketFails) 60 t.Run("Submitting a proposal for new market with bad risk parameter fails", testSubmittingProposalForNewMarketWithBadRiskParameterFails) 61 t.Run("Submitting a proposal for new market with internal time termination with bad risk parameter fails", testSubmittingProposalForNewMarketWithInternalTimeTerminationWithBadRiskParameterFails) 62 t.Run("Submitting a proposal for a ne market without disposal slippage range fails", testSubmittingProposalWithoutDisposalSlippageFails) 63 64 t.Run("Rejecting a proposal for new market succeeds", testRejectingProposalForNewMarketSucceeds) 65 66 t.Run("Voting for a new market proposal succeeds", testVotingForNewMarketProposalSucceeds) 67 t.Run("Voting with a majority of 'yes' makes the new market proposal passed", testVotingWithMajorityOfYesMakesNewMarketProposalPassed) 68 t.Run("Voting with a majority of 'no' makes the new market proposal declined", testVotingWithMajorityOfNoMakesNewMarketProposalDeclined) 69 t.Run("Voting with insufficient participation makes the new market proposal declined", testVotingWithInsufficientParticipationMakesNewMarketProposalDeclined) 70 t.Run("Invalid combination of decimals for market", testInvalidDecimalPlace) 71 } 72 73 func TestProposalForSuccessorMarket(t *testing.T) { 74 t.Run("Submitting a proposal for fully defined successor market succeeds", testSubmittingProposalForFullSuccessorMarketSucceeds) 75 76 t.Run("Reject successor markets with an invalid insurance pool fraction", testRejectSuccessorInvalidInsurancePoolFraction) 77 t.Run("Reject successor market proposal if the product is incompatible", testRejectSuccessorProductMismatch) 78 t.Run("Reject successor market if the parent market does not exist", testRejectSuccessorNoParent) 79 80 t.Run("Remove proposals for an already succeeded market", testRemoveSuccessorsForSucceeded) 81 t.Run("Remove proposals for an already succeeded market on tick", testRemoveSuccessorsForRejectedMarket) 82 } 83 84 func testSubmittingProposalForInvalidCappedMarketFails(t *testing.T) { 85 eng := getTestEngine(t, time.Now()) 86 87 // given 88 party := eng.newValidParty("a-valid-party", 123456789) 89 proposal := eng.newProposalForCapped(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &types.FutureCap{ 90 MaxPrice: num.UintZero(), 91 Binary: true, 92 FullyCollateralised: true, 93 }) 94 95 // setup 96 eng.ensureAllAssetEnabled(t) 97 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 98 99 // when 100 toSubmit, err := eng.submitProposal(t, proposal) 101 102 // then 103 require.Error(t, err) 104 require.Nil(t, toSubmit) 105 106 // another failed scenario is when the max price doesn't respect the tick size 107 proposal = eng.newProposalForCapped(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &types.FutureCap{ 108 MaxPrice: num.NewUint(1234), 109 Binary: true, 110 FullyCollateralised: true, 111 }) 112 nmp := proposal.Terms.GetNewMarket() 113 nmp.Changes.TickSize = num.NewUint(10) 114 115 // setup 116 eng.ensureAllAssetEnabled(t) 117 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 118 119 // when 120 toSubmit, err = eng.submitProposal(t, proposal) 121 122 // then 123 require.Error(t, err) 124 require.Nil(t, toSubmit) 125 } 126 127 func testSubmittingProposalForNewCappedMarketSucceeds(t *testing.T) { 128 eng := getTestEngine(t, time.Now()) 129 130 // given 131 party := eng.newValidParty("a-valid-party", 123456789) 132 proposal := eng.newProposalForCapped(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &types.FutureCap{ 133 MaxPrice: num.NewUint(1000), 134 Binary: true, 135 FullyCollateralised: true, 136 }) 137 138 // setup 139 eng.ensureAllAssetEnabled(t) 140 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 141 142 // when 143 toSubmit, err := eng.submitProposal(t, proposal) 144 145 // then 146 require.NoError(t, err) 147 require.NotNil(t, toSubmit) 148 assert.True(t, toSubmit.IsNewMarket()) 149 require.NotNil(t, toSubmit.NewMarket().Market()) 150 151 // same as above, but with a tick size that is non-zero 152 proposal = eng.newProposalForCapped(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &types.FutureCap{ 153 MaxPrice: num.NewUint(1000), 154 Binary: true, 155 FullyCollateralised: true, 156 }) 157 nmp := proposal.Terms.GetNewMarket() 158 nmp.Changes.TickSize = num.NewUint(10) 159 160 // setup 161 eng.ensureAllAssetEnabled(t) 162 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 163 164 // when 165 toSubmit, err = eng.submitProposal(t, proposal) 166 167 // then 168 require.NoError(t, err) 169 require.NotNil(t, toSubmit) 170 assert.True(t, toSubmit.IsNewMarket()) 171 require.NotNil(t, toSubmit.NewMarket().Market()) 172 } 173 174 func testSubmittingProposalForNewMarketSucceeds(t *testing.T) { 175 eng := getTestEngine(t, time.Now()) 176 177 // given 178 party := eng.newValidParty("a-valid-party", 123456789) 179 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 180 181 // setup 182 eng.ensureAllAssetEnabled(t) 183 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 184 185 // when 186 toSubmit, err := eng.submitProposal(t, proposal) 187 188 // then 189 require.NoError(t, err) 190 require.NotNil(t, toSubmit) 191 assert.True(t, toSubmit.IsNewMarket()) 192 require.NotNil(t, toSubmit.NewMarket().Market()) 193 } 194 195 func testRemoveSuccessorsForRejectedMarket(t *testing.T) { 196 eng := getTestEngine(t, time.Now()) 197 198 // given 199 party := eng.newValidParty("a-valid-party", 123456789) 200 suc := types.SuccessorConfig{ 201 ParentID: "parentID", 202 InsurancePoolFraction: num.DecimalFromFloat(.5), 203 } 204 // add 3 proposals for the same parent 205 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 206 eng.markets.EXPECT().IsSucceeded(suc.ParentID).Times(3).Return(false) 207 filter, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 208 enact := now.Add(24 * time.Hour) 209 proposals := []types.Proposal{ 210 eng.newProposalForSuccessorMarket(party.Id, enact, filter, binding, true, &suc), 211 eng.newProposalForSuccessorMarket(party.Id, enact, filter, binding, true, &suc), 212 eng.newProposalForNewMarket(party.Id, enact, filter, binding, true), // non successor just because 213 eng.newProposalForSuccessorMarket(party.Id, enact, filter, binding, true, &suc), 214 } 215 first := proposals[0] 216 pFuture := first.NewMarket().Changes.GetFuture() 217 eng.ensureAllAssetEnabled(t) 218 for _, p := range proposals { 219 eng.expectOpenProposalEvent(t, party.Id, p.ID) 220 } 221 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(6).Return( 222 types.Market{ 223 TradableInstrument: &types.TradableInstrument{ 224 Instrument: &types.Instrument{ 225 Product: &types.InstrumentFuture{ 226 Future: &types.Future{ 227 SettlementAsset: pFuture.Future.SettlementAsset, 228 QuoteName: pFuture.Future.SettlementAsset, 229 }, 230 }, 231 }, 232 }, 233 }, true) 234 235 // submit all proposals 236 for _, p := range proposals { 237 toSubmit, err := eng.submitProposal(t, p) 238 239 // then 240 require.NoError(t, err) 241 require.NotNil(t, toSubmit) 242 assert.True(t, toSubmit.IsNewMarket()) 243 require.NotNil(t, toSubmit.NewMarket().Market()) 244 } 245 // all proposals will be in the active proposals slice, so let's make sure all of them are removed 246 for _, p := range proposals { 247 if p.IsSuccessorMarket() { 248 eng.markets.EXPECT().GetMarketState(p.ID).Times(1).Return(types.MarketStateRejected, errors.New("foo")) 249 } 250 } 251 expState := types.ProposalStateRejected 252 expError := types.ProposalErrorInvalidSuccessorMarket 253 eng.broker.EXPECT().Send(gomock.Any()).AnyTimes().Do(func(evt events.Event) { 254 pe, ok := evt.(*events.Proposal) 255 require.True(t, ok) 256 prop := pe.Proposal() 257 require.Equal(t, expState, prop.State) 258 require.NotNil(t, prop.Reason) 259 require.EqualValues(t, expError, *prop.Reason) 260 }) 261 eng.OnTick(context.Background(), now.Add(time.Second)) 262 } 263 264 func testRemoveSuccessorsForSucceeded(t *testing.T) { 265 eng := getTestEngine(t, time.Now()) 266 267 // given 268 party := eng.newValidParty("a-valid-party", 123456789) 269 suc := types.SuccessorConfig{ 270 ParentID: "parentID", 271 InsurancePoolFraction: num.DecimalFromFloat(.5), 272 } 273 // add 3 proposals for the same parent 274 eng.markets.EXPECT().IsSucceeded(suc.ParentID).Times(3).Return(false) 275 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 276 filter, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 277 proposals := []types.Proposal{ 278 eng.newProposalForSuccessorMarket(party.Id, now, filter, binding, true, &suc), 279 eng.newProposalForSuccessorMarket(party.Id, now, filter, binding, true, &suc), 280 eng.newProposalForNewMarket(party.Id, now, filter, binding, true), // non successor just because 281 eng.newProposalForSuccessorMarket(party.Id, now, filter, binding, true, &suc), 282 } 283 first := proposals[0] 284 pFuture := first.NewMarket().Changes.GetFuture() 285 eng.ensureAllAssetEnabled(t) 286 for _, p := range proposals { 287 eng.expectOpenProposalEvent(t, party.Id, p.ID) 288 } 289 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(6).Return( 290 types.Market{ 291 TradableInstrument: &types.TradableInstrument{ 292 Instrument: &types.Instrument{ 293 Product: &types.InstrumentFuture{ 294 Future: &types.Future{ 295 SettlementAsset: pFuture.Future.SettlementAsset, 296 QuoteName: pFuture.Future.SettlementAsset, 297 }, 298 }, 299 }, 300 }, 301 }, true) 302 303 // submit all proposals 304 for _, p := range proposals { 305 toSubmit, err := eng.submitProposal(t, p) 306 307 // then 308 require.NoError(t, err) 309 require.NotNil(t, toSubmit) 310 assert.True(t, toSubmit.IsNewMarket()) 311 require.NotNil(t, toSubmit.NewMarket().Market()) 312 } 313 // all proposals will be in the active proposals slice, so let's make sure all of them are removed 314 first.State = types.ProposalStateEnacted 315 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 316 eng.FinaliseEnactment(context.Background(), &first) 317 } 318 319 func testSubmittingProposalForFullSuccessorMarketSucceeds(t *testing.T) { 320 eng := getTestEngine(t, time.Now()) 321 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 322 323 // given 324 party := eng.newValidParty("a-valid-party", 123456789) 325 suc := types.SuccessorConfig{ 326 ParentID: "parentID", 327 InsurancePoolFraction: num.DecimalFromFloat(.5), 328 } 329 eng.markets.EXPECT().IsSucceeded(suc.ParentID).Times(1).Return(false) 330 filter, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 331 proposal := eng.newProposalForSuccessorMarket(party.Id, now, filter, binding, true, &suc) 332 // returns a pointer directly to the change, but reassign just in case it doesn't 333 nm := proposal.NewMarket() 334 // ensure price monitoring params are set 335 if nm.Changes.PriceMonitoringParameters == nil { 336 nm.Changes.PriceMonitoringParameters = &types.PriceMonitoringParameters{ 337 Triggers: []*types.PriceMonitoringTrigger{ 338 { 339 Horizon: 5, 340 HorizonDec: num.DecimalFromFloat(5), 341 Probability: num.DecimalFromFloat(.95), 342 AuctionExtension: 1, 343 }, 344 }, 345 } 346 } 347 // ensure risk model params are set 348 if nm.Changes.RiskParameters == nil { 349 nm.Changes.RiskParameters = &types.NewMarketConfigurationSimple{ 350 Simple: &types.SimpleModelParams{}, 351 } 352 } 353 proposal.Terms.Change = &types.ProposalTermsNewMarket{ 354 NewMarket: nm, 355 } 356 357 // setup 358 eng.ensureAllAssetEnabled(t) 359 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 360 // GetMarket will be called in validateChange & intoSubmit 361 pFuture := proposal.NewMarket().Changes.GetFuture() 362 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(2).Return( 363 types.Market{ 364 TradableInstrument: &types.TradableInstrument{ 365 Instrument: &types.Instrument{ 366 Product: &types.InstrumentFuture{ 367 Future: &types.Future{ 368 SettlementAsset: pFuture.Future.SettlementAsset, 369 QuoteName: pFuture.Future.SettlementAsset, 370 }, 371 }, 372 }, 373 }, 374 }, true) 375 376 // when 377 toSubmit, err := eng.submitProposal(t, proposal) 378 379 // then 380 require.NoError(t, err) 381 require.NotNil(t, toSubmit) 382 assert.True(t, toSubmit.IsNewMarket()) 383 require.NotNil(t, toSubmit.NewMarket().Market()) 384 } 385 386 func testRejectSuccessorInvalidInsurancePoolFraction(t *testing.T) { 387 eng := getTestEngine(t, time.Now()) 388 389 // given 390 party := eng.newValidParty("a-valid-party", 123456789) 391 suc := types.SuccessorConfig{ 392 ParentID: "parentID", 393 InsurancePoolFraction: num.DecimalFromFloat(5), // out of range 0-1 394 } 395 proposal := eng.newProposalForSuccessorMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &suc) 396 397 // setup 398 eng.ensureAllAssetEnabled(t) 399 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidSuccessorMarket) 400 // GetMarket will only be called once, the second call will never happen due to the insurance pool fraction being invalid 401 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(1).Return(types.Market{}, true) // market can be empty, we won't access the settlement/quote stuff 402 403 // when 404 toSubmit, err := eng.submitProposal(t, proposal) 405 406 // then 407 require.Error(t, err) 408 require.Nil(t, toSubmit) 409 } 410 411 func testRejectSuccessorProductMismatch(t *testing.T) { 412 eng := getTestEngine(t, time.Now()) 413 414 // given 415 party := eng.newValidParty("a-valid-party", 123456789) 416 suc := types.SuccessorConfig{ 417 ParentID: "parentID", 418 InsurancePoolFraction: num.DecimalFromFloat(0), 419 } 420 proposal := eng.newProposalForSuccessorMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false, &suc) 421 422 // setup 423 eng.ensureAllAssetEnabled(t) 424 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidSuccessorMarket) 425 // GetMarket will only be called once, the second call will never happen due to the product mismatch 426 fProduct := proposal.NewMarket().Changes.GetFuture() 427 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(1).Return( 428 types.Market{ 429 TradableInstrument: &types.TradableInstrument{ 430 Instrument: &types.Instrument{ 431 Product: &types.InstrumentFuture{ 432 Future: &types.Future{ 433 SettlementAsset: fmt.Sprintf("not%s", fProduct.Future.SettlementAsset), 434 QuoteName: fmt.Sprintf("not%s", fProduct.Future.QuoteName), 435 }, 436 }, 437 }, 438 }, 439 }, true) 440 441 // when 442 toSubmit, err := eng.submitProposal(t, proposal) 443 444 // then 445 require.Error(t, err) 446 require.Nil(t, toSubmit) 447 } 448 449 func testRejectSuccessorNoParent(t *testing.T) { 450 eng := getTestEngine(t, time.Now()) 451 452 // given 453 party := eng.newValidParty("a-valid-party", 123456789) 454 suc := types.SuccessorConfig{ 455 ParentID: "parentID", 456 InsurancePoolFraction: num.DecimalFromFloat(0), 457 } 458 proposal := eng.newProposalForSuccessorMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true, &suc) 459 460 // setup 461 eng.ensureAllAssetEnabled(t) 462 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidSuccessorMarket) 463 // only called once, validateChange already flags this error (missing parent) 464 eng.markets.EXPECT().GetMarket(suc.ParentID, true).Times(1).Return(types.Market{}, false) 465 466 // when 467 toSubmit, err := eng.submitProposal(t, proposal) 468 469 // then 470 require.Error(t, err) 471 require.Nil(t, toSubmit) 472 } 473 474 func testSubmittingProposalWithInternalTimeTerminationForNewMarketSucceeds(t *testing.T) { 475 eng := getTestEngine(t, time.Now()) 476 477 // given 478 party := eng.newValidParty("a-valid-party", 123456789) 479 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false) 480 481 // setup 482 eng.ensureAllAssetEnabled(t) 483 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 484 485 // when 486 toSubmit, err := eng.submitProposal(t, proposal) 487 488 // then 489 require.NoError(t, err) 490 require.NotNil(t, toSubmit) 491 assert.True(t, toSubmit.IsNewMarket()) 492 require.NotNil(t, toSubmit.NewMarket().Market()) 493 } 494 495 func testSubmittingProposalWithInternalTimeSettlingForNewMarketFails(t *testing.T) { 496 eng := getTestEngine(t, time.Now()) 497 498 // given 499 party := eng.newValidParty("a-valid-party", 123456789) 500 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 501 id := eng.newProposalID() 502 tm := now.Add(time.Hour * 24 * 365) 503 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 504 505 proposal := types.Proposal{ 506 ID: id, 507 Reference: "ref-" + id, 508 Party: party.Id, 509 State: types.ProposalStateOpen, 510 Terms: &types.ProposalTerms{ 511 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 512 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 513 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 514 Change: &types.ProposalTermsNewMarket{ 515 NewMarket: &types.NewMarket{ 516 Changes: &types.NewMarketConfiguration{ 517 Instrument: &types.InstrumentConfiguration{ 518 Name: "June 2020 GBP vs VUSD future", 519 Code: "CRYPTO:GBPVUSD/JUN20", 520 Product: &types.InstrumentConfigurationFuture{ 521 Future: &types.FutureProduct{ 522 SettlementAsset: "VUSD", 523 QuoteName: "VUSD", 524 DataSourceSpecForSettlementData: *datasource.NewDefinition( 525 datasource.ContentTypeOracle, 526 ).SetTimeTriggerConditionConfig( 527 []*dstypes.SpecCondition{ 528 { 529 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 530 Value: "0", 531 }, 532 }, 533 ), 534 DataSourceSpecForTradingTermination: *datasource.NewDefinition( 535 datasource.ContentTypeOracle, 536 ).SetTimeTriggerConditionConfig( 537 []*dstypes.SpecCondition{ 538 { 539 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 540 Value: fmt.Sprintf("%d", tm.UnixNano()), 541 }, 542 }), 543 DataSourceSpecBinding: termBinding, 544 }, 545 }, 546 }, 547 RiskParameters: &types.NewMarketConfigurationLogNormal{ 548 LogNormal: &types.LogNormalRiskModel{ 549 RiskAversionParameter: num.DecimalFromFloat(0.01), 550 Tau: num.DecimalFromFloat(0.00011407711613050422), 551 Params: &types.LogNormalModelParams{ 552 Mu: num.DecimalZero(), 553 R: num.DecimalFromFloat(0.016), 554 Sigma: num.DecimalFromFloat(0.09), 555 }, 556 }, 557 }, 558 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 559 DecimalPlaces: 0, 560 LiquiditySLAParameters: &types.LiquiditySLAParams{ 561 PriceRange: num.DecimalFromFloat(0.95), 562 }, 563 LinearSlippageFactor: num.DecimalFromFloat(0.1), 564 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 565 LiquidationStrategy: &types.LiquidationStrategy{ 566 DisposalTimeStep: 10 * time.Second, 567 DisposalFraction: num.DecimalFromFloat(0.1), 568 FullDisposalSize: 20, 569 MaxFractionConsumed: num.DecimalFromFloat(0.01), 570 DisposalSlippage: num.DecimalFromFloat(0.1), 571 }, 572 TickSize: num.UintOne(), 573 }, 574 }, 575 }, 576 }, 577 Rationale: &types.ProposalRationale{ 578 Description: "some description", 579 }, 580 } 581 582 // setup 583 eng.ensureAllAssetEnabled(t) 584 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 585 586 // when 587 toSubmit, err := eng.submitProposal(t, proposal) 588 589 // then 590 assert.Error(t, err, governance.ErrSettlementWithInternalDataSourceIsNotAllowed) 591 require.Nil(t, toSubmit) 592 } 593 594 func testSubmittingProposalWithEmptySettlingDataForNewMarketFails(t *testing.T) { 595 eng := getTestEngine(t, time.Now()) 596 597 // given 598 party := eng.newValidParty("a-valid-party", 123456789) 599 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 600 id := eng.newProposalID() 601 tm := now.Add(time.Hour * 24 * 365) 602 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 603 604 proposal := types.Proposal{ 605 ID: id, 606 Reference: "ref-" + id, 607 Party: party.Id, 608 State: types.ProposalStateOpen, 609 Terms: &types.ProposalTerms{ 610 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 611 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 612 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 613 Change: &types.ProposalTermsNewMarket{ 614 NewMarket: &types.NewMarket{ 615 Changes: &types.NewMarketConfiguration{ 616 Instrument: &types.InstrumentConfiguration{ 617 Name: "June 2020 GBP vs VUSD future", 618 Code: "CRYPTO:GBPVUSD/JUN20", 619 Product: &types.InstrumentConfigurationFuture{ 620 Future: &types.FutureProduct{ 621 SettlementAsset: "VUSD", 622 QuoteName: "VUSD", 623 DataSourceSpecForSettlementData: dsdefinition.Definition{}, 624 DataSourceSpecForTradingTermination: dsdefinition.Definition{}, 625 DataSourceSpecBinding: termBinding, 626 }, 627 }, 628 }, 629 RiskParameters: &types.NewMarketConfigurationLogNormal{ 630 LogNormal: &types.LogNormalRiskModel{ 631 RiskAversionParameter: num.DecimalFromFloat(0.01), 632 Tau: num.DecimalFromFloat(0.00011407711613050422), 633 Params: &types.LogNormalModelParams{ 634 Mu: num.DecimalZero(), 635 R: num.DecimalFromFloat(0.016), 636 Sigma: num.DecimalFromFloat(0.09), 637 }, 638 }, 639 }, 640 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 641 DecimalPlaces: 0, 642 LiquiditySLAParameters: &types.LiquiditySLAParams{ 643 PriceRange: num.DecimalFromFloat(0.95), 644 }, 645 LinearSlippageFactor: num.DecimalFromFloat(0.1), 646 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 647 LiquidationStrategy: &types.LiquidationStrategy{ 648 DisposalTimeStep: 10 * time.Second, 649 DisposalFraction: num.DecimalFromFloat(0.1), 650 FullDisposalSize: 20, 651 MaxFractionConsumed: num.DecimalFromFloat(0.01), 652 DisposalSlippage: num.DecimalFromFloat(0.1), 653 }, 654 TickSize: num.UintOne(), 655 }, 656 }, 657 }, 658 }, 659 Rationale: &types.ProposalRationale{ 660 Description: "some description", 661 }, 662 } 663 664 // setup 665 eng.ensureAllAssetEnabled(t) 666 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 667 668 // when 669 toSubmit, err := eng.submitProposal(t, proposal) 670 671 // then 672 assert.Error(t, err, governance.ErrMissingDataSourceSpecForSettlementData) 673 require.Nil(t, toSubmit) 674 } 675 676 func testSubmittingProposalWithEmptyTerminationDataForNewMarketFails(t *testing.T) { 677 eng := getTestEngine(t, time.Now()) 678 679 // given 680 party := eng.newValidParty("a-valid-party", 123456789) 681 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 682 id := eng.newProposalID() 683 tm := now.Add(time.Hour * 24 * 365) 684 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 685 686 proposal := types.Proposal{ 687 ID: id, 688 Reference: "ref-" + id, 689 Party: party.Id, 690 State: types.ProposalStateOpen, 691 Terms: &types.ProposalTerms{ 692 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 693 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 694 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 695 Change: &types.ProposalTermsNewMarket{ 696 NewMarket: &types.NewMarket{ 697 Changes: &types.NewMarketConfiguration{ 698 Instrument: &types.InstrumentConfiguration{ 699 Name: "June 2020 GBP vs VUSD future", 700 Code: "CRYPTO:GBPVUSD/JUN20", 701 Product: &types.InstrumentConfigurationFuture{ 702 Future: &types.FutureProduct{ 703 SettlementAsset: "VUSD", 704 QuoteName: "VUSD", 705 DataSourceSpecForSettlementData: *datasource.NewDefinition( 706 datasource.ContentTypeInternalTimeTermination, 707 ).SetTimeTriggerConditionConfig( 708 []*dstypes.SpecCondition{ 709 { 710 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 711 Value: "0", 712 }, 713 }, 714 ), 715 DataSourceSpecForTradingTermination: dsdefinition.Definition{}, 716 DataSourceSpecBinding: termBinding, 717 }, 718 }, 719 }, 720 RiskParameters: &types.NewMarketConfigurationLogNormal{ 721 LogNormal: &types.LogNormalRiskModel{ 722 RiskAversionParameter: num.DecimalFromFloat(0.01), 723 Tau: num.DecimalFromFloat(0.00011407711613050422), 724 Params: &types.LogNormalModelParams{ 725 Mu: num.DecimalZero(), 726 R: num.DecimalFromFloat(0.016), 727 Sigma: num.DecimalFromFloat(0.09), 728 }, 729 }, 730 }, 731 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 732 DecimalPlaces: 0, 733 LiquiditySLAParameters: &types.LiquiditySLAParams{ 734 PriceRange: num.DecimalFromFloat(0.95), 735 }, 736 LinearSlippageFactor: num.DecimalFromFloat(0.1), 737 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 738 LiquidationStrategy: &types.LiquidationStrategy{ 739 DisposalTimeStep: 10 * time.Second, 740 DisposalFraction: num.DecimalFromFloat(0.1), 741 FullDisposalSize: 20, 742 MaxFractionConsumed: num.DecimalFromFloat(0.01), 743 DisposalSlippage: num.DecimalFromFloat(0.1), 744 }, 745 TickSize: num.UintOne(), 746 }, 747 }, 748 }, 749 }, 750 Rationale: &types.ProposalRationale{ 751 Description: "some description", 752 }, 753 } 754 755 // setup 756 eng.ensureAllAssetEnabled(t) 757 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 758 759 // when 760 toSubmit, err := eng.submitProposal(t, proposal) 761 762 // then 763 assert.Error(t, err, governance.ErrMissingDataSourceSpecForTradingTermination) 764 require.Nil(t, toSubmit) 765 } 766 767 func testSubmittingProposalWithInternalTimeTerminationWithLessThanEqualConditionForNewMarketFails(t *testing.T) { 768 eng := getTestEngine(t, time.Now()) 769 770 // given 771 party := eng.newValidParty("a-valid-party", 123456789) 772 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 773 id := eng.newProposalID() 774 tm := now.Add(time.Hour * 24 * 365) 775 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 776 777 settl := datasource.NewDefinition( 778 datasource.ContentTypeOracle, 779 ).SetOracleConfig( 780 &signedoracle.SpecConfiguration{ 781 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 782 Filters: []*dstypes.SpecFilter{ 783 { 784 Key: &dstypes.SpecPropertyKey{ 785 Name: "prices.ETH.value", 786 Type: datapb.PropertyKey_TYPE_INTEGER, 787 }, 788 Conditions: []*dstypes.SpecCondition{ 789 { 790 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 791 Value: "0", 792 }, 793 }, 794 }, 795 }, 796 }, 797 ) 798 799 term := datasource.NewDefinition( 800 datasource.ContentTypeOracle, 801 ).SetTimeTriggerConditionConfig( 802 []*dstypes.SpecCondition{ 803 { 804 Operator: datapb.Condition_OPERATOR_LESS_THAN, 805 Value: fmt.Sprintf("%d", tm.UnixNano()), 806 }, 807 }) 808 809 riskParameters := types.NewMarketConfigurationLogNormal{ 810 LogNormal: &types.LogNormalRiskModel{ 811 RiskAversionParameter: num.DecimalFromFloat(0.01), 812 Tau: num.DecimalFromFloat(0.00011407711613050422), 813 Params: &types.LogNormalModelParams{ 814 Mu: num.DecimalZero(), 815 R: num.DecimalFromFloat(0.016), 816 Sigma: num.DecimalFromFloat(0.09), 817 }, 818 }, 819 } 820 821 proposal := types.Proposal{ 822 ID: id, 823 Reference: "ref-" + id, 824 Party: party.Id, 825 State: types.ProposalStateOpen, 826 Terms: &types.ProposalTerms{ 827 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 828 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 829 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 830 Change: &types.ProposalTermsNewMarket{ 831 NewMarket: &types.NewMarket{ 832 Changes: &types.NewMarketConfiguration{ 833 Instrument: &types.InstrumentConfiguration{ 834 Name: "June 2020 GBP vs VUSD future", 835 Code: "CRYPTO:GBPVUSD/JUN20", 836 Product: &types.InstrumentConfigurationFuture{ 837 Future: &types.FutureProduct{ 838 SettlementAsset: "VUSD", 839 QuoteName: "VUSD", 840 DataSourceSpecForSettlementData: *settl, 841 DataSourceSpecForTradingTermination: *term, 842 DataSourceSpecBinding: termBinding, 843 }, 844 }, 845 }, 846 RiskParameters: &riskParameters, 847 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 848 DecimalPlaces: 0, 849 LiquiditySLAParameters: &types.LiquiditySLAParams{ 850 PriceRange: num.DecimalFromFloat(0.95), 851 }, 852 LinearSlippageFactor: num.DecimalFromFloat(0.1), 853 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 854 LiquidationStrategy: &types.LiquidationStrategy{ 855 DisposalTimeStep: 10 * time.Second, 856 DisposalFraction: num.DecimalFromFloat(0.1), 857 FullDisposalSize: 20, 858 MaxFractionConsumed: num.DecimalFromFloat(0.01), 859 DisposalSlippage: num.DecimalFromFloat(0.1), 860 }, 861 TickSize: num.UintOne(), 862 }, 863 }, 864 }, 865 }, 866 Rationale: &types.ProposalRationale{ 867 Description: "some description", 868 }, 869 } 870 871 // setup 872 eng.ensureAllAssetEnabled(t) 873 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 874 875 // when 876 toSubmit, err := eng.submitProposal(t, proposal) 877 878 // then 879 assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition) 880 require.Nil(t, toSubmit) 881 882 term = datasource.NewDefinition( 883 datasource.ContentTypeOracle, 884 ).SetTimeTriggerConditionConfig( 885 []*dstypes.SpecCondition{ 886 { 887 Operator: datapb.Condition_OPERATOR_LESS_THAN_OR_EQUAL, 888 Value: fmt.Sprintf("%d", tm.UnixNano()), 889 }, 890 }) 891 892 proposal = types.Proposal{ 893 ID: id, 894 Reference: "ref-" + id, 895 Party: party.Id, 896 State: types.ProposalStateOpen, 897 Terms: &types.ProposalTerms{ 898 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 899 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 900 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 901 Change: &types.ProposalTermsNewMarket{ 902 NewMarket: &types.NewMarket{ 903 Changes: &types.NewMarketConfiguration{ 904 Instrument: &types.InstrumentConfiguration{ 905 Name: "June 2020 GBP vs VUSD future", 906 Code: "CRYPTO:GBPVUSD/JUN20", 907 Product: &types.InstrumentConfigurationFuture{ 908 Future: &types.FutureProduct{ 909 SettlementAsset: "VUSD", 910 QuoteName: "VUSD", 911 DataSourceSpecForSettlementData: *settl, 912 DataSourceSpecForTradingTermination: *term, 913 DataSourceSpecBinding: termBinding, 914 }, 915 }, 916 }, 917 RiskParameters: &riskParameters, 918 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 919 DecimalPlaces: 0, 920 LiquiditySLAParameters: &types.LiquiditySLAParams{ 921 PriceRange: num.DecimalFromFloat(0.95), 922 }, 923 LinearSlippageFactor: num.DecimalFromFloat(0.1), 924 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 925 LiquidationStrategy: &types.LiquidationStrategy{ 926 DisposalTimeStep: 10 * time.Second, 927 DisposalFraction: num.DecimalFromFloat(0.1), 928 FullDisposalSize: 20, 929 MaxFractionConsumed: num.DecimalFromFloat(0.01), 930 DisposalSlippage: num.DecimalFromFloat(0.1), 931 }, 932 TickSize: num.UintOne(), 933 }, 934 }, 935 }, 936 }, 937 Rationale: &types.ProposalRationale{ 938 Description: "some description", 939 }, 940 } 941 942 // setup 943 eng.ensureAllAssetEnabled(t) 944 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 945 946 // when 947 toSubmit, err = eng.submitProposal(t, proposal) 948 949 // then 950 assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition) 951 require.Nil(t, toSubmit) 952 } 953 954 func testSubmittingProposalWithExternalWithInternalTimeTerminationKeyForNewMarketSucceeds(t *testing.T) { 955 eng := getTestEngine(t, time.Now()) 956 957 // given 958 party := eng.newValidParty("a-valid-party", 123456789) 959 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 960 filter, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 961 proposal := eng.newProposalForNewMarket(party.Id, now.Add(2*time.Hour), filter, binding, true) 962 963 // setup 964 eng.ensureAllAssetEnabled(t) 965 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 966 967 // when 968 toSubmit, err := eng.submitProposal(t, proposal) 969 970 // then 971 require.NoError(t, err) 972 require.NotNil(t, toSubmit) 973 assert.True(t, toSubmit.IsNewMarket()) 974 require.NotNil(t, toSubmit.NewMarket().Market()) 975 } 976 977 func testSubmittingProposalWithInternalTimeTriggerTerminationFails(t *testing.T) { 978 eng := getTestEngine(t, time.Now()) 979 980 // given 981 party := eng.newValidParty("a-valid-party", 123456789) 982 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 983 id := eng.newProposalID() 984 tm := now.Add(time.Hour * 24 * 365) 985 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 986 987 settl := datasource.NewDefinition( 988 datasource.ContentTypeOracle, 989 ).SetOracleConfig( 990 &signedoracle.SpecConfiguration{ 991 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 992 Filters: []*dstypes.SpecFilter{ 993 { 994 Key: &dstypes.SpecPropertyKey{ 995 Name: "prices.ETH.value", 996 Type: datapb.PropertyKey_TYPE_INTEGER, 997 }, 998 Conditions: []*dstypes.SpecCondition{ 999 { 1000 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 1001 Value: "0", 1002 }, 1003 }, 1004 }, 1005 }, 1006 }, 1007 ) 1008 1009 term := datasource.NewDefinition( 1010 datasource.ContentTypeInternalTimeTriggerTermination, 1011 ).SetTimeTriggerConditionConfig( 1012 []*dstypes.SpecCondition{ 1013 { 1014 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 1015 Value: fmt.Sprintf("%d", tm.UnixNano()), 1016 }, 1017 }) 1018 1019 riskParameters := types.NewMarketConfigurationLogNormal{ 1020 LogNormal: &types.LogNormalRiskModel{ 1021 RiskAversionParameter: num.DecimalFromFloat(0.01), 1022 Tau: num.DecimalFromFloat(0.00011407711613050422), 1023 Params: &types.LogNormalModelParams{ 1024 Mu: num.DecimalZero(), 1025 R: num.DecimalFromFloat(0.016), 1026 Sigma: num.DecimalFromFloat(0.09), 1027 }, 1028 }, 1029 } 1030 1031 proposal := types.Proposal{ 1032 ID: id, 1033 Reference: "ref-" + id, 1034 Party: party.Id, 1035 State: types.ProposalStateOpen, 1036 Terms: &types.ProposalTerms{ 1037 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 1038 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 1039 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 1040 Change: &types.ProposalTermsNewMarket{ 1041 NewMarket: &types.NewMarket{ 1042 Changes: &types.NewMarketConfiguration{ 1043 Instrument: &types.InstrumentConfiguration{ 1044 Name: "June 2020 GBP vs VUSD future", 1045 Code: "CRYPTO:GBPVUSD/JUN20", 1046 Product: &types.InstrumentConfigurationFuture{ 1047 Future: &types.FutureProduct{ 1048 SettlementAsset: "VUSD", 1049 QuoteName: "VUSD", 1050 DataSourceSpecForSettlementData: *settl, 1051 DataSourceSpecForTradingTermination: *term, 1052 DataSourceSpecBinding: termBinding, 1053 }, 1054 }, 1055 }, 1056 RiskParameters: &riskParameters, 1057 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1058 DecimalPlaces: 0, 1059 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1060 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1061 LiquidationStrategy: &types.LiquidationStrategy{ 1062 DisposalTimeStep: 10 * time.Second, 1063 DisposalFraction: num.DecimalFromFloat(0.1), 1064 FullDisposalSize: 20, 1065 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1066 DisposalSlippage: num.DecimalFromFloat(0.1), 1067 }, 1068 TickSize: num.UintOne(), 1069 }, 1070 }, 1071 }, 1072 }, 1073 Rationale: &types.ProposalRationale{ 1074 Description: "some description", 1075 }, 1076 } 1077 1078 // setup 1079 eng.ensureAllAssetEnabled(t) 1080 // expect 1081 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 1082 1083 // when 1084 toSubmit, err := eng.submitProposal(t, proposal) 1085 1086 // then 1087 assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed) 1088 require.Nil(t, toSubmit) 1089 } 1090 1091 func testSubmittingProposalWithInternalTimeTriggerSettlementFails(t *testing.T) { 1092 eng := getTestEngine(t, time.Now()) 1093 1094 // given 1095 party := eng.newValidParty("a-valid-party", 123456789) 1096 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 1097 id := eng.newProposalID() 1098 tm := now.Add(time.Hour * 24 * 365) 1099 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 1100 1101 settl := datasource.NewDefinition( 1102 datasource.ContentTypeInternalTimeTriggerTermination, 1103 ).SetTimeTriggerConditionConfig( 1104 []*dstypes.SpecCondition{ 1105 { 1106 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 1107 Value: fmt.Sprintf("%d", tm.UnixNano()), 1108 }, 1109 }) 1110 1111 term := datasource.NewDefinition( 1112 datasource.ContentTypeOracle, 1113 ).SetTimeTriggerConditionConfig( 1114 []*dstypes.SpecCondition{ 1115 { 1116 Operator: datapb.Condition_OPERATOR_LESS_THAN, 1117 Value: fmt.Sprintf("%d", tm.UnixNano()), 1118 }, 1119 }) 1120 1121 riskParameters := types.NewMarketConfigurationLogNormal{ 1122 LogNormal: &types.LogNormalRiskModel{ 1123 RiskAversionParameter: num.DecimalFromFloat(0.01), 1124 Tau: num.DecimalFromFloat(0.00011407711613050422), 1125 Params: &types.LogNormalModelParams{ 1126 Mu: num.DecimalZero(), 1127 R: num.DecimalFromFloat(0.016), 1128 Sigma: num.DecimalFromFloat(0.09), 1129 }, 1130 }, 1131 } 1132 1133 proposal := types.Proposal{ 1134 ID: id, 1135 Reference: "ref-" + id, 1136 Party: party.Id, 1137 State: types.ProposalStateOpen, 1138 Terms: &types.ProposalTerms{ 1139 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 1140 EnactmentTimestamp: now.Add(2 * 48 * time.Hour).Unix(), 1141 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 1142 Change: &types.ProposalTermsNewMarket{ 1143 NewMarket: &types.NewMarket{ 1144 Changes: &types.NewMarketConfiguration{ 1145 Instrument: &types.InstrumentConfiguration{ 1146 Name: "June 2020 GBP vs VUSD future", 1147 Code: "CRYPTO:GBPVUSD/JUN20", 1148 Product: &types.InstrumentConfigurationFuture{ 1149 Future: &types.FutureProduct{ 1150 SettlementAsset: "VUSD", 1151 QuoteName: "VUSD", 1152 DataSourceSpecForSettlementData: *settl, 1153 DataSourceSpecForTradingTermination: *term, 1154 DataSourceSpecBinding: termBinding, 1155 }, 1156 }, 1157 }, 1158 RiskParameters: &riskParameters, 1159 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1160 DecimalPlaces: 0, 1161 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1162 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1163 LiquidationStrategy: &types.LiquidationStrategy{ 1164 DisposalTimeStep: 10 * time.Second, 1165 DisposalFraction: num.DecimalFromFloat(0.1), 1166 FullDisposalSize: 20, 1167 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1168 DisposalSlippage: num.DecimalFromFloat(0.1), 1169 }, 1170 TickSize: num.UintOne(), 1171 }, 1172 }, 1173 }, 1174 }, 1175 Rationale: &types.ProposalRationale{ 1176 Description: "some description", 1177 }, 1178 } 1179 1180 // setup 1181 eng.ensureAllAssetEnabled(t) 1182 // expect 1183 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidFutureProduct) 1184 1185 // when 1186 toSubmit, err := eng.submitProposal(t, proposal) 1187 1188 // then 1189 assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed) 1190 require.Nil(t, toSubmit) 1191 } 1192 1193 func testSubmittingDuplicatedProposalForNewMarketFails(t *testing.T) { 1194 eng := getTestEngine(t, time.Now()) 1195 1196 // given 1197 party := vgrand.RandomStr(5) 1198 proposal := eng.newProposalForNewMarket(party, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1199 1200 // setup 1201 eng.ensureTokenBalanceForParty(t, party, 1000) 1202 eng.ensureAllAssetEnabled(t) 1203 1204 // expect 1205 eng.expectOpenProposalEvent(t, party, proposal.ID) 1206 1207 // when 1208 _, err := eng.submitProposal(t, proposal) 1209 1210 // then 1211 require.NoError(t, err) 1212 1213 // given 1214 duplicatedProposal := proposal 1215 duplicatedProposal.Reference = "this-is-a-copy" 1216 1217 // when 1218 _, err = eng.submitProposal(t, duplicatedProposal) 1219 1220 // then 1221 require.Error(t, err) 1222 assert.EqualError(t, governance.ErrProposalIsDuplicate, err.Error()) 1223 1224 // given 1225 duplicatedProposal = proposal 1226 duplicatedProposal.State = types.ProposalStatePassed 1227 1228 // when 1229 _, err = eng.submitProposal(t, duplicatedProposal) 1230 1231 // then 1232 require.Error(t, err) 1233 assert.EqualError(t, governance.ErrProposalIsDuplicate, err.Error(), "reject attempt to change state indirectly") 1234 } 1235 1236 func testSubmittingDuplicatedProposalWithInternalTimeTerminationForNewMarketFails(t *testing.T) { 1237 eng := getTestEngine(t, time.Now()) 1238 1239 // given 1240 party := vgrand.RandomStr(5) 1241 proposal := eng.newProposalForNewMarket(party, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false) 1242 1243 // setup 1244 eng.ensureTokenBalanceForParty(t, party, 1000) 1245 eng.ensureAllAssetEnabled(t) 1246 1247 // expect 1248 eng.expectOpenProposalEvent(t, party, proposal.ID) 1249 1250 // when 1251 _, err := eng.submitProposal(t, proposal) 1252 1253 // then 1254 require.NoError(t, err) 1255 1256 // given 1257 duplicatedProposal := proposal 1258 duplicatedProposal.Reference = "this-is-a-copy" 1259 1260 // when 1261 _, err = eng.submitProposal(t, duplicatedProposal) 1262 1263 // then 1264 require.Error(t, err) 1265 assert.EqualError(t, governance.ErrProposalIsDuplicate, err.Error()) 1266 1267 // given 1268 duplicatedProposal = proposal 1269 duplicatedProposal.State = types.ProposalStatePassed 1270 1271 // when 1272 _, err = eng.submitProposal(t, duplicatedProposal) 1273 1274 // then 1275 require.Error(t, err) 1276 assert.EqualError(t, governance.ErrProposalIsDuplicate, err.Error(), "reject attempt to change state indirectly") 1277 } 1278 1279 func testSubmittingProposalForNewMarketWithBadRiskParameterFails(t *testing.T) { 1280 eng := getTestEngine(t, time.Now()) 1281 1282 // given 1283 party := eng.newValidParty("a-valid-party", 1) 1284 eng.ensureAllAssetEnabled(t) 1285 1286 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1287 proposal.Terms.GetNewMarket().Changes.RiskParameters = &types.NewMarketConfigurationLogNormal{ 1288 LogNormal: &types.LogNormalRiskModel{ 1289 Params: nil, // it's nil by zero value, but eh, let's show that's what we test 1290 }, 1291 } 1292 1293 // setup 1294 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 1295 1296 // when 1297 _, err := eng.submitProposal(t, proposal) 1298 1299 // then 1300 require.Error(t, err) 1301 assert.Contains(t, err.Error(), "invalid risk parameter") 1302 } 1303 1304 func testSubmittingProposalForNewMarketWithInternalTimeTerminationWithBadRiskParameterFails(t *testing.T) { 1305 eng := getTestEngine(t, time.Now()) 1306 1307 // given 1308 party := eng.newValidParty("a-valid-party", 1) 1309 eng.ensureAllAssetEnabled(t) 1310 1311 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false) 1312 proposal.Terms.GetNewMarket().Changes.RiskParameters = &types.NewMarketConfigurationLogNormal{ 1313 LogNormal: &types.LogNormalRiskModel{ 1314 Params: nil, // it's nil by zero value, but eh, let's show that's what we test 1315 }, 1316 } 1317 1318 // setup 1319 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 1320 1321 // when 1322 _, err := eng.submitProposal(t, proposal) 1323 1324 // then 1325 require.Error(t, err) 1326 assert.Contains(t, err.Error(), "invalid risk parameter") 1327 } 1328 1329 func testSubmittingProposalWithoutDisposalSlippageFails(t *testing.T) { 1330 eng := getTestEngine(t, time.Now()) 1331 // given 1332 party := eng.newValidParty("a-valid-party", 1) 1333 eng.ensureAllAssetEnabled(t) 1334 1335 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, false) 1336 proposal.Terms.GetNewMarket().Changes.LiquidationStrategy = &types.LiquidationStrategy{ 1337 DisposalTimeStep: time.Second * 10, 1338 DisposalFraction: num.DecimalFromFloat(0.2), 1339 FullDisposalSize: 10, 1340 MaxFractionConsumed: num.DecimalFromFloat(0.5), 1341 } 1342 1343 // setup 1344 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 1345 1346 // when 1347 _, err := eng.submitProposal(t, proposal) 1348 1349 // then 1350 require.Error(t, err) 1351 assert.Contains(t, err.Error(), "liquidation strategy must specify a disposal slippage range > 0") 1352 } 1353 1354 func testOutOfRangeRiskParamFail(t *testing.T, lnm *types.LogNormalRiskModel) { 1355 t.Helper() 1356 eng := getTestEngine(t, time.Now()) 1357 1358 // given 1359 party := eng.newValidParty("a-valid-party", 1) 1360 eng.ensureAllAssetEnabled(t) 1361 1362 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1363 proposal.Terms.GetNewMarket().Changes.RiskParameters = &types.NewMarketConfigurationLogNormal{LogNormal: lnm} 1364 1365 // setup 1366 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 1367 1368 // when 1369 _, err := eng.submitProposal(t, proposal) 1370 1371 // then 1372 require.Error(t, err) 1373 assert.Contains(t, err.Error(), "invalid risk parameter") 1374 } 1375 1376 func TestSubmittingProposalForNewMarketWithOutOfRangeRiskParameterFails(t *testing.T) { 1377 lnm := &types.LogNormalRiskModel{} 1378 lnm.RiskAversionParameter = num.DecimalFromFloat(1e-8 - 1e-12) 1379 testOutOfRangeRiskParamFail(t, lnm) 1380 lnm.RiskAversionParameter = num.DecimalFromFloat(1e1 + 1e-12) 1381 testOutOfRangeRiskParamFail(t, lnm) 1382 lnm.RiskAversionParameter = num.DecimalFromFloat(1e-6) 1383 lnm.Tau = num.DecimalFromFloat(1e-8 - 1e-12) 1384 testOutOfRangeRiskParamFail(t, lnm) 1385 lnm.Tau = num.DecimalFromFloat(1 + 1e-12) 1386 testOutOfRangeRiskParamFail(t, lnm) 1387 lnm.Tau = num.DecimalOne() 1388 lnm.Params = &types.LogNormalModelParams{} 1389 lnm.Params.Mu = num.DecimalFromFloat(-1e-6 - 1e-12) 1390 testOutOfRangeRiskParamFail(t, lnm) 1391 lnm.Params.Mu = num.DecimalFromFloat(1e-6 + 1e-12) 1392 testOutOfRangeRiskParamFail(t, lnm) 1393 lnm.Params.Mu = num.DecimalFromFloat(0.0) 1394 lnm.Params.R = num.DecimalFromFloat(-1 - 1e-12) 1395 testOutOfRangeRiskParamFail(t, lnm) 1396 lnm.Params.R = num.DecimalFromFloat(1 + 1e-12) 1397 testOutOfRangeRiskParamFail(t, lnm) 1398 lnm.Params.R = num.DecimalFromFloat(0.0) 1399 lnm.Params.Sigma = num.DecimalFromFloat(1e-3 - 1e-12) 1400 testOutOfRangeRiskParamFail(t, lnm) 1401 lnm.Params.Sigma = num.DecimalFromFloat(50 + 1e-12) 1402 testOutOfRangeRiskParamFail(t, lnm) 1403 lnm.Params.Sigma = num.DecimalFromFloat(1.0) 1404 1405 // now all risk params are valid 1406 eng := getTestEngine(t, time.Now()) 1407 1408 // given 1409 party := eng.newValidParty("a-valid-party", 1) 1410 eng.ensureAllAssetEnabled(t) 1411 1412 proposal := eng.newProposalForNewMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1413 proposal.Terms.GetNewMarket().Changes.RiskParameters = &types.NewMarketConfigurationLogNormal{LogNormal: lnm} 1414 1415 // setup 1416 eng.broker.EXPECT().Send(gomock.Any()).Times(1) 1417 1418 // when 1419 _, err := eng.submitProposal(t, proposal) 1420 1421 // then 1422 require.NoError(t, err) 1423 } 1424 1425 func testRejectingProposalForNewMarketSucceeds(t *testing.T) { 1426 eng := getTestEngine(t, time.Now()) 1427 1428 // given 1429 party := vgrand.RandomStr(5) 1430 proposal := eng.newProposalForNewMarket(party, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1431 1432 // setup 1433 eng.ensureAllAssetEnabled(t) 1434 eng.ensureTokenBalanceForParty(t, party, 10000) 1435 1436 // expect 1437 eng.expectOpenProposalEvent(t, party, proposal.ID) 1438 1439 // when 1440 toSubmit, err := eng.submitProposal(t, proposal) 1441 1442 // then 1443 require.NoError(t, err) 1444 require.NotNil(t, toSubmit) 1445 1446 // expect 1447 eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorCouldNotInstantiateMarket) 1448 1449 // when 1450 err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError) 1451 1452 // then 1453 require.NoError(t, err) 1454 1455 // when 1456 // Just one more time to make sure it was removed from proposals. 1457 err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError) 1458 1459 // then 1460 assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error()) 1461 } 1462 1463 func testVotingForNewMarketProposalSucceeds(t *testing.T) { 1464 eng := getTestEngine(t, time.Now()) 1465 1466 // given 1467 proposer := vgrand.RandomStr(5) 1468 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1469 1470 // setup 1471 eng.ensureAllAssetEnabled(t) 1472 eng.ensureTokenBalanceForParty(t, proposer, 1) 1473 1474 // expect 1475 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1476 1477 // when 1478 _, err := eng.submitProposal(t, proposal) 1479 1480 // then 1481 require.NoError(t, err) 1482 1483 // given 1484 voter := vgrand.RandomStr(5) 1485 1486 // setup 1487 eng.ensureTokenBalanceForParty(t, voter, 1) 1488 1489 // expect 1490 eng.expectVoteEvent(t, voter, proposal.ID) 1491 1492 // when 1493 err = eng.addYesVote(t, voter, proposal.ID) 1494 1495 // then 1496 require.NoError(t, err) 1497 } 1498 1499 func testVotingWithMajorityOfYesMakesNewMarketProposalPassed(t *testing.T) { 1500 eng := getTestEngine(t, time.Now()) 1501 1502 // when 1503 proposer := vgrand.RandomStr(5) 1504 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1505 1506 // setup 1507 eng.ensureStakingAssetTotalSupply(t, 9) 1508 eng.ensureAllAssetEnabled(t) 1509 eng.ensureTokenBalanceForParty(t, proposer, 1) 1510 1511 // expect 1512 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1513 1514 // when 1515 _, err := eng.submitProposal(t, proposal) 1516 1517 // then 1518 require.NoError(t, err) 1519 1520 // given 1521 voter1 := vgrand.RandomStr(5) 1522 1523 // setup 1524 eng.ensureTokenBalanceForParty(t, voter1, 7) 1525 1526 // expect 1527 eng.expectVoteEvent(t, voter1, proposal.ID) 1528 1529 // then 1530 err = eng.addYesVote(t, voter1, proposal.ID) 1531 1532 // then 1533 require.NoError(t, err) 1534 1535 // given 1536 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1537 1538 // setup 1539 eng.ensureTokenBalanceForParty(t, voter1, 7) 1540 1541 // expect 1542 eng.expectPassedProposalEvent(t, proposal.ID) 1543 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7") 1544 eng.expectGetMarketState(t, proposal.ID) 1545 1546 // when 1547 eng.OnTick(context.Background(), afterClosing) 1548 1549 // given 1550 voter2 := vgrand.RandomStr(5) 1551 1552 // when 1553 err = eng.addNoVote(t, voter2, proposal.ID) 1554 1555 // then 1556 require.Error(t, err) 1557 assert.EqualError(t, err, governance.ErrProposalNotOpenForVotes.Error()) 1558 1559 // given 1560 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1561 1562 // when 1563 // no calculations, no state change, simply removed from governance engine 1564 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1565 1566 // then 1567 require.Len(t, toBeEnacted, 1) 1568 assert.Equal(t, proposal.ID, toBeEnacted[0].Proposal().ID) 1569 1570 // when 1571 err = eng.addNoVote(t, voter2, proposal.ID) 1572 1573 // then 1574 require.Error(t, err) 1575 assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error()) 1576 } 1577 1578 func testVotingWithMajorityOfNoMakesNewMarketProposalDeclined(t *testing.T) { 1579 eng := getTestEngine(t, time.Now()) 1580 1581 // given 1582 proposer := vgrand.RandomStr(5) 1583 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1584 1585 // setup 1586 eng.ensureAllAssetEnabled(t) 1587 eng.ensureStakingAssetTotalSupply(t, 200) 1588 eng.ensureTokenBalanceForParty(t, proposer, 100) 1589 1590 // expect 1591 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1592 1593 // when 1594 _, err := eng.submitProposal(t, proposal) 1595 1596 // then 1597 require.NoError(t, err) 1598 1599 // given 1600 voter := vgrand.RandomStr(5) 1601 1602 // setup 1603 eng.ensureTokenBalanceForParty(t, voter, 100) 1604 1605 // expect 1606 eng.expectVoteEvent(t, voter, proposal.ID) 1607 1608 // when 1609 err = eng.addYesVote(t, voter, proposal.ID) 1610 1611 // then 1612 require.NoError(t, err) 1613 1614 // setup 1615 eng.ensureTokenBalanceForParty(t, voter, 100) 1616 1617 // setup 1618 eng.expectVoteEvent(t, voter, proposal.ID) 1619 1620 // when 1621 err = eng.addNoVote(t, voter, proposal.ID) 1622 1623 // then 1624 require.NoError(t, err) 1625 1626 // given 1627 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1628 1629 // setup 1630 eng.ensureTokenBalanceForParty(t, voter, 100) 1631 1632 // expect 1633 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached) 1634 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "100") 1635 eng.expectGetMarketState(t, proposal.ID) 1636 1637 // when 1638 _, voteClosed := eng.OnTick(context.Background(), afterClosing) 1639 1640 // then 1641 require.Len(t, voteClosed, 1) 1642 vc := voteClosed[0] 1643 require.NotNil(t, vc.NewMarket()) 1644 assert.True(t, vc.NewMarket().Rejected()) 1645 1646 // given 1647 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1648 1649 // when 1650 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1651 1652 // then 1653 assert.Empty(t, toBeEnacted) 1654 } 1655 1656 func testVotingWithInsufficientParticipationMakesNewMarketProposalDeclined(t *testing.T) { 1657 eng := getTestEngine(t, time.Now()) 1658 1659 // given 1660 proposer := vgrand.RandomStr(5) 1661 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1662 1663 // setup 1664 eng.ensureAllAssetEnabled(t) 1665 eng.ensureStakingAssetTotalSupply(t, 800) 1666 eng.ensureTokenBalanceForParty(t, proposer, 100) 1667 1668 // expect 1669 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1670 1671 // when 1672 _, err := eng.submitProposal(t, proposal) 1673 1674 // then 1675 require.NoError(t, err) 1676 1677 // given 1678 voter := vgrand.RandomStr(5) 1679 1680 // setup 1681 eng.ensureTokenBalanceForParty(t, voter, 100) 1682 1683 // expect 1684 eng.expectVoteEvent(t, voter, proposal.ID) 1685 1686 // when 1687 err = eng.addYesVote(t, voter, proposal.ID) 1688 1689 // then 1690 require.NoError(t, err) 1691 1692 // given 1693 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1694 1695 // setup 1696 eng.ensureTokenBalanceForParty(t, voter, 100) 1697 1698 // expect 1699 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached) 1700 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "100") 1701 eng.expectGetMarketState(t, proposal.ID) 1702 // when 1703 _, voteClosed := eng.OnTick(context.Background(), afterClosing) 1704 1705 // then 1706 require.Len(t, voteClosed, 1) 1707 vc := voteClosed[0] 1708 require.NotNil(t, vc.NewMarket()) 1709 assert.True(t, vc.NewMarket().Rejected()) 1710 1711 // given 1712 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1713 1714 // when 1715 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 1716 1717 // then 1718 assert.Empty(t, toBeEnacted) 1719 } 1720 1721 func testSubmittingProposalForNewPerpsMarketSucceeds(t *testing.T) { 1722 eng := getTestEngine(t, time.Now()) 1723 defer eng.ctrl.Finish() 1724 1725 // given 1726 party := eng.newValidParty("a-valid-party", 123456789) 1727 proposal := eng.newProposalForNewPerpsMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1728 1729 // setup 1730 eng.ensureAllAssetEnabled(t) 1731 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 1732 1733 // when 1734 toSubmit, err := eng.submitProposal(t, proposal) 1735 require.NoError(t, err) 1736 1737 // the proposal had a nil initial time for the time trigger. 1738 // ensure it was set to the enactment time. 1739 tt := toSubmit.Proposal().Terms.GetNewMarket().Changes.Instrument. 1740 Product.(*types.InstrumentConfigurationPerps). 1741 Perps.DataSourceSpecForSettlementSchedule. 1742 GetInternalTimeTriggerSpecConfiguration().Triggers[0] 1743 1744 enactmentTime := toSubmit.Proposal().Terms.EnactmentTimestamp 1745 1746 assert.NotNil(t, tt.Initial) 1747 assert.Equal(t, tt.Initial.Unix(), enactmentTime) 1748 1749 // then 1750 require.NoError(t, err) 1751 require.NotNil(t, toSubmit) 1752 assert.True(t, toSubmit.IsNewMarket()) 1753 require.NotNil(t, toSubmit.NewMarket().Market()) 1754 } 1755 1756 func testSubmittingProposalForNewPerpsMarketWithCustomInitialTimeSucceeds(t *testing.T) { 1757 eng := getTestEngine(t, time.Now()) 1758 defer eng.ctrl.Finish() 1759 1760 // given 1761 party := eng.newValidParty("a-valid-party", 123456789) 1762 proposal := eng.newProposalForNewPerpsMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1763 1764 // set the time differently to start e.g sometimes after the enactment ti 1765 enactAt := proposal.Terms.EnactmentTimestamp 1766 proposal.Terms.Change.(*types.ProposalTermsNewMarket).NewMarket.Changes.Instrument.Product.(*types.InstrumentConfigurationPerps).Perps.DataSourceSpecForSettlementSchedule.GetInternalTimeTriggerSpecConfiguration().Triggers[0].Initial = ptr.From(time.Unix(enactAt, 0).Add(60 * time.Minute)) 1767 1768 // setup 1769 eng.ensureAllAssetEnabled(t) 1770 eng.expectOpenProposalEvent(t, party.Id, proposal.ID) 1771 1772 // when 1773 toSubmit, err := eng.submitProposal(t, proposal) 1774 require.NoError(t, err) 1775 1776 // the proposal had a nil initial time for the time trigger. 1777 // ensure it was set to the enactment time. 1778 tt := toSubmit.Proposal().Terms.GetNewMarket().Changes.Instrument. 1779 Product.(*types.InstrumentConfigurationPerps). 1780 Perps.DataSourceSpecForSettlementSchedule. 1781 GetInternalTimeTriggerSpecConfiguration().Triggers[0] 1782 1783 enactmentTime := toSubmit.Proposal().Terms.EnactmentTimestamp 1784 1785 assert.NotNil(t, tt.Initial) 1786 assert.NotEqual(t, tt.Initial.Unix(), enactmentTime) 1787 1788 // then 1789 require.NoError(t, err) 1790 require.NotNil(t, toSubmit) 1791 assert.True(t, toSubmit.IsNewMarket()) 1792 require.NotNil(t, toSubmit.NewMarket().Market()) 1793 } 1794 1795 func testSubmittingProposalForNewPerpsMarketWithPastInitialTimeFails(t *testing.T) { 1796 eng := getTestEngine(t, time.Now()) 1797 defer eng.ctrl.Finish() 1798 1799 // given 1800 party := eng.newValidParty("a-valid-party", 123456789) 1801 proposal := eng.newProposalForNewPerpsMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1802 1803 now := eng.tsvc.GetTimeNow() 1804 proposal.Terms.Change.(*types.ProposalTermsNewMarket).NewMarket.Changes.Instrument.Product.(*types.InstrumentConfigurationPerps).Perps.DataSourceSpecForSettlementSchedule.GetInternalTimeTriggerSpecConfiguration().Triggers[0].Initial = ptr.From(now.Add(-(60 * time.Second))) 1805 1806 // setup 1807 eng.ensureAllAssetEnabled(t) 1808 1809 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidPerpsProduct) 1810 1811 // when 1812 toSubmit, err := eng.submitProposal(t, proposal) 1813 1814 // then 1815 require.EqualError(t, err, "time trigger starts in the past") 1816 require.Nil(t, toSubmit) 1817 } 1818 1819 func testInvalidDecimalPlace(t *testing.T) { 1820 eng := getTestEngine(t, time.Now()) 1821 defer eng.ctrl.Finish() 1822 1823 // given 1824 party := eng.newValidParty("a-valid-party", 123456789) 1825 proposal := eng.newProposalForNewPerpsMarket(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1826 1827 proposal.Terms.GetNewMarket().Changes.DecimalPlaces = 12 1828 proposal.Terms.GetNewMarket().Changes.PositionDecimalPlaces = 7 1829 1830 // set the time differently to start e.g sometimes after the enactment ti 1831 enactAt := proposal.Terms.EnactmentTimestamp 1832 proposal.Terms.Change.(*types.ProposalTermsNewMarket).NewMarket.Changes.Instrument.Product.(*types.InstrumentConfigurationPerps).Perps.DataSourceSpecForSettlementSchedule.GetInternalTimeTriggerSpecConfiguration().Triggers[0].Initial = ptr.From(time.Unix(enactAt, 0).Add(60 * time.Minute)) 1833 1834 // setup 1835 eng.ensureAllAssetEnabled(t) 1836 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorTooManyMarketDecimalPlaces) 1837 1838 // when 1839 _, err := eng.submitProposal(t, proposal) 1840 require.Equal(t, "market decimal + position decimals must be less than or equal to asset decimals", err.Error()) 1841 }