code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_update_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/netparams" 32 "code.vegaprotocol.io/vega/core/types" 33 "code.vegaprotocol.io/vega/libs/num" 34 vgrand "code.vegaprotocol.io/vega/libs/rand" 35 datapb "code.vegaprotocol.io/vega/protos/vega/data/v1" 36 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 ) 40 41 func TestProposalForMarketUpdate(t *testing.T) { 42 t.Run("Submitting a proposal for market update succeeds", testSubmittingProposalForMarketUpdateSucceeds) 43 t.Run("Submitting a proposal for market update with internal time termination succeeds", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationSucceeds) 44 t.Run("Submitting a proposal for market update with internal settling fails", testSubmittingProposalForMarketUpdateWithInternalTimeSetllingFails) 45 t.Run("Submitting a proposal for market update with internal time termination and 'less than' condition fails", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationWithLessThanConditionFails) 46 t.Run("Submitting a proposal for market update with termination in the past succeeds", testSubmittingProposalForMarketUpdateWithEarlyTerminationSucceeds) 47 t.Run("Submitting a proposal for market update with external termination using internal time key succeeds", testSubmittingProposalForMarketUpdateWithExternalSourceUsingInternalKeyTimeForTerminationSucceeds) 48 t.Run("Submitting a proposal for market update with empty settlement data fails", testSubmittingProposalForMarketUpdateWithEmptySettlementDataFails) 49 t.Run("Submitting a proposal for market update with empty termination data fails", testSubmittingProposalForMarketUpdateWithEmptyTerminationDataFails) 50 t.Run("Submitting a proposal for market update on unknown market fails", testSubmittingProposalForMarketUpdateForUnknownMarketFails) 51 t.Run("Submitting a proposal with internal time termination for market update on unknown market fails", testSubmittingProposalForMarketUpdateWithInternalTimeTerminationForUnknownMarketFails) 52 t.Run("Submitting a proposal with internal time trigger termination fails", testSubmittingProposalForMarketUpdateWithInternalTimeTriggerTerminationFails) 53 t.Run("Submitting a proposal with internal time trigger settlement fails", testSubmittingProposalForMarketUpdateWithInternalTimeTriggerSettlementFails) 54 55 t.Run("Submitting a proposal for market update for not-enacted market fails", testSubmittingProposalForMarketUpdateForNotEnactedMarketFails) 56 t.Run("Submitting a proposal for market update with insufficient equity-like share fails", testSubmittingProposalForMarketUpdateWithInsufficientEquityLikeShareFails) 57 t.Run("Pre-enactment of market update proposal succeeds", testPreEnactmentOfMarketUpdateSucceeds) 58 t.Run("Pre-enactment of market with internal time termination update proposal succeeds", testPreEnactmentOfMarketUpdateWithInternalTimeTerminationSucceeds) 59 60 t.Run("Rejecting a proposal for market update succeeds", testRejectingProposalForMarketUpdateSucceeds) 61 62 t.Run("Voting without reaching minimum of tokens and equity-like shares makes the market update proposal declined", testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesMarketUpdateProposalPassed) 63 t.Run("Voting with a majority of 'yes' from tokens makes the market update proposal passed", testVotingWithMajorityOfYesFromTokenHoldersMakesMarketUpdateProposalPassed) 64 t.Run("Voting with a majority of 'no' from tokens makes the market update proposal declined", testVotingWithMajorityOfNoFromTokenHoldersMakesMarketUpdateProposalDeclined) 65 t.Run("Voting without reaching minimum of tokens and a majority of 'yes' from equity-like shares makes the market update proposal passed", testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesMarketUpdateProposalPassed) 66 t.Run("Voting without reaching minimum of tokens and a majority of 'no' from equity-like shares makes the market update proposal declined", testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesMarketUpdateProposalDeclined) 67 t.Run("Submitting a proposal with inconsistent products fails", TestSubmitProposalWithInconsistentProductFails) 68 } 69 70 func TestSubmittingProposalForMarketUpdateSucceeds(t *testing.T) { 71 eng := getTestEngine(t, time.Now()) 72 73 ctx := context.Background() 74 75 // custom settings here: 76 eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredParticipationLP", "0")).Times(1) 77 require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredParticipationLP", "0")) 78 79 eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredParticipation", "0.07")).Times(1) 80 require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredParticipation", "0.07")) 81 82 eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredMajority", "0.66")).Times(1) 83 require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredMajority", "0.66")) 84 85 eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, "governance.proposal.updateMarket.requiredMajorityLP", "0.66")).Times(1) 86 require.NoError(t, eng.netp.Update(ctx, "governance.proposal.updateMarket.requiredMajorityLP", "0.66")) 87 88 // given 89 proposer := vgrand.RandomStr(5) 90 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 91 marketID := proposal.MarketUpdate().MarketID 92 93 // setup 94 eng.ensureTokenBalanceForParty(t, proposer, 1000) 95 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.29) 96 eng.ensureExistingMarket(t, marketID) 97 eng.ensureGetMarketFuture(t, marketID) 98 99 // expect 100 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 101 102 // when 103 toSubmit, err := eng.submitProposal(t, proposal) 104 105 // then 106 require.NoError(t, err) 107 require.NotNil(t, toSubmit) 108 109 // setup 110 voterWithELS := vgrand.RandomStr(5) 111 eng.ensureTokenBalanceForParty(t, voterWithELS, 0) 112 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1) 113 114 // expect 115 eng.expectVoteEvent(t, voterWithELS, proposal.ID) 116 117 // when 118 err = eng.addYesVote(t, voterWithELS, proposal.ID) 119 120 // then 121 require.NoError(t, err) 122 123 // Closing the proposal. 124 // given 125 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 126 127 // setup 128 eng.ensureStakingAssetTotalSupply(t, 1000000) 129 eng.ensureTokenBalanceForParty(t, voterWithELS, 0) 130 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.29) 131 132 // // expect 133 eng.expectPassedProposalEvent(t, proposal.ID) 134 eng.expectVoteEvents(t) 135 eng.expectGetMarketState(t, proposal.ID) 136 137 // // when 138 eng.OnTick(context.Background(), afterClosing) 139 } 140 141 func testSubmittingProposalForMarketUpdateSucceeds(t *testing.T) { 142 eng := getTestEngine(t, time.Now()) 143 144 // given 145 proposer := vgrand.RandomStr(5) 146 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 147 marketID := proposal.MarketUpdate().MarketID 148 149 // setup 150 eng.ensureTokenBalanceForParty(t, proposer, 1000) 151 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 152 eng.ensureExistingMarket(t, marketID) 153 eng.ensureGetMarketFuture(t, marketID) 154 155 // expect 156 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 157 158 // when 159 toSubmit, err := eng.submitProposal(t, proposal) 160 161 // then 162 require.NoError(t, err) 163 require.NotNil(t, toSubmit) 164 } 165 166 func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationSucceeds(t *testing.T) { 167 eng := getTestEngine(t, time.Now()) 168 169 // given 170 proposer := vgrand.RandomStr(5) 171 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false) 172 marketID := proposal.MarketUpdate().MarketID 173 174 // setup 175 eng.ensureTokenBalanceForParty(t, proposer, 1000) 176 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 177 eng.ensureExistingMarket(t, marketID) 178 eng.ensureGetMarketFuture(t, marketID) 179 180 // expect 181 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 182 183 // when 184 toSubmit, err := eng.submitProposal(t, proposal) 185 186 // then 187 require.NoError(t, err) 188 require.NotNil(t, toSubmit) 189 } 190 191 func testSubmittingProposalForMarketUpdateWithInternalTimeSetllingFails(t *testing.T) { 192 eng := getTestEngine(t, time.Now()) 193 194 // given 195 proposer := vgrand.RandomStr(5) 196 197 id := eng.newProposalID() 198 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 199 tm := now.Add(time.Hour * 24 * 365) 200 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 201 202 termination := datasource.NewDefinition( 203 datasource.ContentTypeInternalTimeTermination, 204 ).SetTimeTriggerConditionConfig( 205 []*dstypes.SpecCondition{ 206 { 207 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 208 Value: fmt.Sprintf("%d", tm.UnixNano()), 209 }, 210 }, 211 ) 212 213 proposal := types.Proposal{ 214 ID: "market-1", 215 Reference: "ref-" + id, 216 Party: proposer, 217 State: types.ProposalStateOpen, 218 Terms: &types.ProposalTerms{ 219 ClosingTimestamp: now.Add(96 * time.Hour).Unix(), 220 EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), 221 ValidationTimestamp: now.Add(2 * time.Hour).Unix(), 222 Change: &types.ProposalTermsUpdateMarket{ 223 UpdateMarket: &types.UpdateMarket{ 224 MarketID: vgrand.RandomStr(5), 225 Changes: &types.UpdateMarketConfiguration{ 226 Instrument: &types.UpdateInstrumentConfiguration{ 227 Code: "CRYPTO:GBPVUSD/JUN20", 228 Name: "CRYPTO:GBPVUSD/JUN20", 229 Product: &types.UpdateInstrumentConfigurationFuture{ 230 Future: &types.UpdateFutureProduct{ 231 QuoteName: "VUSD", 232 DataSourceSpecForSettlementData: *datasource.NewDefinition( 233 datasource.ContentTypeOracle, 234 ).SetTimeTriggerConditionConfig( 235 []*dstypes.SpecCondition{ 236 { 237 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 238 Value: fmt.Sprintf("%d", tm.UnixNano()), 239 }, 240 }, 241 ), 242 DataSourceSpecForTradingTermination: *termination, 243 DataSourceSpecBinding: termBinding, 244 }, 245 }, 246 }, 247 RiskParameters: &types.UpdateMarketConfigurationLogNormal{ 248 LogNormal: &types.LogNormalRiskModel{ 249 RiskAversionParameter: num.DecimalFromFloat(0.01), 250 Tau: num.DecimalFromFloat(0.00011407711613050422), 251 Params: &types.LogNormalModelParams{ 252 Mu: num.DecimalZero(), 253 R: num.DecimalFromFloat(0.016), 254 Sigma: num.DecimalFromFloat(0.09), 255 }, 256 }, 257 }, 258 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 259 LiquiditySLAParameters: &types.LiquiditySLAParams{ 260 PriceRange: num.DecimalFromFloat(0.95), 261 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 262 PerformanceHysteresisEpochs: 4, 263 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 264 }, 265 LinearSlippageFactor: num.DecimalFromFloat(0.1), 266 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 267 LiquidationStrategy: &types.LiquidationStrategy{ 268 DisposalTimeStep: 10 * time.Second, 269 DisposalFraction: num.DecimalFromFloat(0.1), 270 FullDisposalSize: 20, 271 MaxFractionConsumed: num.DecimalFromFloat(0.01), 272 }, 273 TickSize: num.UintOne(), 274 }, 275 }, 276 }, 277 }, 278 Rationale: &types.ProposalRationale{ 279 Description: "some description", 280 }, 281 } 282 283 marketID := proposal.MarketUpdate().MarketID 284 // setup 285 eng.ensureTokenBalanceForParty(t, proposer, 1000) 286 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 287 eng.ensureExistingMarket(t, marketID) 288 eng.ensureGetMarketFuture(t, marketID) 289 290 // expect 291 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 292 293 // when 294 toSubmit, err := eng.submitProposal(t, proposal) 295 296 // then 297 assert.Error(t, err, governance.ErrSettlementWithInternalDataSourceIsNotAllowed) 298 require.Nil(t, toSubmit) 299 } 300 301 func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationWithLessThanConditionFails(t *testing.T) { 302 eng := getTestEngine(t, time.Now()) 303 304 // given 305 proposer := vgrand.RandomStr(5) 306 307 id := eng.newProposalID() 308 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 309 tm := now.Add(time.Hour * 24 * 365) 310 311 _, termBinding := produceTimeTriggeredDataSourceSpec(tm) 312 313 settl := datasource.NewDefinition( 314 datasource.ContentTypeOracle, 315 ).SetOracleConfig( 316 &signedoracle.SpecConfiguration{ 317 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 318 Filters: []*dstypes.SpecFilter{ 319 { 320 Key: &dstypes.SpecPropertyKey{ 321 Name: "prices.ETH.value", 322 Type: datapb.PropertyKey_TYPE_INTEGER, 323 }, 324 Conditions: []*dstypes.SpecCondition{}, 325 }, 326 }, 327 }, 328 ) 329 330 term := datasource.NewDefinition( 331 datasource.ContentTypeInternalTimeTermination, 332 ).SetTimeTriggerConditionConfig( 333 []*dstypes.SpecCondition{ 334 { 335 Operator: datapb.Condition_OPERATOR_LESS_THAN, 336 Value: fmt.Sprintf("%d", tm.UnixNano()), 337 }, 338 }, 339 ) 340 341 riskParameters := types.UpdateMarketConfigurationLogNormal{ 342 LogNormal: &types.LogNormalRiskModel{ 343 RiskAversionParameter: num.DecimalFromFloat(0.01), 344 Tau: num.DecimalFromFloat(0.00011407711613050422), 345 Params: &types.LogNormalModelParams{ 346 Mu: num.DecimalZero(), 347 R: num.DecimalFromFloat(0.016), 348 Sigma: num.DecimalFromFloat(0.09), 349 }, 350 }, 351 } 352 353 proposal := types.Proposal{ 354 ID: "market-1", 355 Reference: "ref-" + id, 356 Party: proposer, 357 State: types.ProposalStateOpen, 358 Terms: &types.ProposalTerms{ 359 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 360 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 361 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 362 Change: &types.ProposalTermsUpdateMarket{ 363 UpdateMarket: &types.UpdateMarket{ 364 MarketID: vgrand.RandomStr(5), 365 Changes: &types.UpdateMarketConfiguration{ 366 Instrument: &types.UpdateInstrumentConfiguration{ 367 Code: "CRYPTO:GBPVUSD/JUN20", 368 Name: "CRYPTO:GBPVUSD/JUN20", 369 Product: &types.UpdateInstrumentConfigurationFuture{ 370 Future: &types.UpdateFutureProduct{ 371 QuoteName: "VUSD", 372 DataSourceSpecForSettlementData: *settl, 373 DataSourceSpecForTradingTermination: *term, 374 DataSourceSpecBinding: termBinding, 375 }, 376 }, 377 }, 378 RiskParameters: &riskParameters, 379 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 380 LiquiditySLAParameters: &types.LiquiditySLAParams{ 381 PriceRange: num.DecimalFromFloat(0.95), 382 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 383 PerformanceHysteresisEpochs: 4, 384 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 385 }, 386 LinearSlippageFactor: num.DecimalFromFloat(0.1), 387 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 388 LiquidationStrategy: &types.LiquidationStrategy{ 389 DisposalTimeStep: 10 * time.Second, 390 DisposalFraction: num.DecimalFromFloat(0.1), 391 FullDisposalSize: 20, 392 MaxFractionConsumed: num.DecimalFromFloat(0.01), 393 }, 394 TickSize: num.UintOne(), 395 }, 396 }, 397 }, 398 }, 399 Rationale: &types.ProposalRationale{ 400 Description: "some description", 401 }, 402 } 403 404 marketID := proposal.MarketUpdate().MarketID 405 406 // setup 407 eng.ensureTokenBalanceForParty(t, proposer, 1000) 408 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 409 eng.ensureExistingMarket(t, marketID) 410 eng.ensureGetMarketFuture(t, marketID) 411 412 // expect 413 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 414 415 // when 416 toSubmit, err := eng.submitProposal(t, proposal) 417 418 // then 419 assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition) 420 require.Nil(t, toSubmit) 421 422 term = datasource.NewDefinition( 423 datasource.ContentTypeOracle, 424 ).SetTimeTriggerConditionConfig( 425 []*dstypes.SpecCondition{ 426 { 427 Operator: datapb.Condition_OPERATOR_LESS_THAN_OR_EQUAL, 428 Value: fmt.Sprintf("%d", tm.UnixNano()), 429 }, 430 }, 431 ) 432 433 proposal = types.Proposal{ 434 ID: "market-1", 435 Reference: "ref-" + id, 436 Party: proposer, 437 State: types.ProposalStateOpen, 438 Terms: &types.ProposalTerms{ 439 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 440 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 441 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 442 Change: &types.ProposalTermsUpdateMarket{ 443 UpdateMarket: &types.UpdateMarket{ 444 MarketID: vgrand.RandomStr(5), 445 Changes: &types.UpdateMarketConfiguration{ 446 Instrument: &types.UpdateInstrumentConfiguration{ 447 Code: "CRYPTO:GBPVUSD/JUN20", 448 Name: "CRYPTO:GBPVUSD/JUN20", 449 Product: &types.UpdateInstrumentConfigurationFuture{ 450 Future: &types.UpdateFutureProduct{ 451 QuoteName: "VUSD", 452 DataSourceSpecForSettlementData: *settl, 453 DataSourceSpecForTradingTermination: *term, 454 DataSourceSpecBinding: termBinding, 455 }, 456 }, 457 }, 458 RiskParameters: &riskParameters, 459 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 460 LiquiditySLAParameters: &types.LiquiditySLAParams{ 461 PriceRange: num.DecimalFromFloat(0.95), 462 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 463 PerformanceHysteresisEpochs: 4, 464 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 465 }, 466 LinearSlippageFactor: num.DecimalFromFloat(0.1), 467 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 468 LiquidationStrategy: &types.LiquidationStrategy{ 469 DisposalTimeStep: 10 * time.Second, 470 DisposalFraction: num.DecimalFromFloat(0.1), 471 FullDisposalSize: 20, 472 MaxFractionConsumed: num.DecimalFromFloat(0.01), 473 }, 474 TickSize: num.UintOne(), 475 }, 476 }, 477 }, 478 }, 479 Rationale: &types.ProposalRationale{ 480 Description: "some description", 481 }, 482 } 483 484 marketID = proposal.MarketUpdate().MarketID 485 486 // setup 487 eng.ensureTokenBalanceForParty(t, proposer, 1000) 488 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 489 eng.ensureExistingMarket(t, marketID) 490 eng.ensureGetMarketFuture(t, marketID) 491 492 // expect 493 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 494 495 // when 496 toSubmit, err = eng.submitProposal(t, proposal) 497 498 // then 499 assert.Error(t, err, dserrors.ErrDataSourceSpecHasInvalidTimeCondition) 500 501 require.Nil(t, toSubmit) 502 } 503 504 func testSubmittingProposalForMarketUpdateWithExternalSourceUsingInternalKeyTimeForTerminationSucceeds(t *testing.T) { 505 eng := getTestEngine(t, time.Now()) 506 507 // given 508 proposer := vgrand.RandomStr(5) 509 filter, binding := produceTimeTriggeredDataSourceSpec(time.Now()) 510 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), filter, binding, true) 511 marketID := proposal.MarketUpdate().MarketID 512 513 // setup 514 eng.ensureTokenBalanceForParty(t, proposer, 1000) 515 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 516 eng.ensureExistingMarket(t, marketID) 517 eng.ensureGetMarketFuture(t, marketID) 518 519 // expect 520 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 521 522 // when 523 toSubmit, err := eng.submitProposal(t, proposal) 524 525 // then 526 require.NoError(t, err) 527 require.NotNil(t, toSubmit) 528 } 529 530 func testSubmittingProposalForMarketUpdateWithEmptySettlementDataFails(t *testing.T) { 531 eng := getTestEngine(t, time.Now()) 532 533 // given 534 proposer := vgrand.RandomStr(5) 535 id := eng.newProposalID() 536 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 537 tm := now.Add(time.Hour * 24 * 365) 538 _, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 539 term := datasource.NewDefinition( 540 datasource.ContentTypeInternalTimeTermination, 541 ).SetTimeTriggerConditionConfig( 542 []*dstypes.SpecCondition{ 543 { 544 Operator: datapb.Condition_OPERATOR_LESS_THAN, 545 Value: fmt.Sprintf("%d", tm.UnixNano()), 546 }, 547 }, 548 ) 549 550 riskParameters := types.UpdateMarketConfigurationLogNormal{ 551 LogNormal: &types.LogNormalRiskModel{ 552 RiskAversionParameter: num.DecimalFromFloat(0.01), 553 Tau: num.DecimalFromFloat(0.00011407711613050422), 554 Params: &types.LogNormalModelParams{ 555 Mu: num.DecimalZero(), 556 R: num.DecimalFromFloat(0.016), 557 Sigma: num.DecimalFromFloat(0.09), 558 }, 559 }, 560 } 561 proposal := types.Proposal{ 562 ID: "market-1", 563 Reference: "ref-" + id, 564 Party: proposer, 565 State: types.ProposalStateOpen, 566 Terms: &types.ProposalTerms{ 567 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 568 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 569 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 570 Change: &types.ProposalTermsUpdateMarket{ 571 UpdateMarket: &types.UpdateMarket{ 572 MarketID: vgrand.RandomStr(5), 573 Changes: &types.UpdateMarketConfiguration{ 574 Instrument: &types.UpdateInstrumentConfiguration{ 575 Code: "CRYPTO:GBPVUSD/JUN20", 576 Name: "CRYPTO:GBPVUSD/JUN20", 577 Product: &types.UpdateInstrumentConfigurationFuture{ 578 Future: &types.UpdateFutureProduct{ 579 QuoteName: "VUSD", 580 DataSourceSpecForSettlementData: dsdefinition.Definition{}, 581 DataSourceSpecForTradingTermination: *term, 582 DataSourceSpecBinding: binding, 583 }, 584 }, 585 }, 586 RiskParameters: &riskParameters, 587 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 588 LiquiditySLAParameters: &types.LiquiditySLAParams{ 589 PriceRange: num.DecimalFromFloat(0.95), 590 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 591 PerformanceHysteresisEpochs: 4, 592 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 593 }, 594 LinearSlippageFactor: num.DecimalFromFloat(0.1), 595 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 596 LiquidationStrategy: &types.LiquidationStrategy{ 597 DisposalTimeStep: 10 * time.Second, 598 DisposalFraction: num.DecimalFromFloat(0.1), 599 FullDisposalSize: 20, 600 MaxFractionConsumed: num.DecimalFromFloat(0.01), 601 }, 602 TickSize: num.UintOne(), 603 }, 604 }, 605 }, 606 }, 607 Rationale: &types.ProposalRationale{ 608 Description: "some description", 609 }, 610 } 611 marketID := proposal.MarketUpdate().MarketID 612 613 // setup 614 eng.ensureTokenBalanceForParty(t, proposer, 1000) 615 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 616 eng.ensureExistingMarket(t, marketID) 617 eng.ensureGetMarketFuture(t, marketID) 618 619 // expect 620 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 621 622 // when 623 toSubmit, err := eng.submitProposal(t, proposal) 624 625 // then 626 assert.Error(t, err, governance.ErrMissingDataSourceSpecForSettlementData) 627 require.Nil(t, toSubmit) 628 } 629 630 func testSubmittingProposalForMarketUpdateWithEmptyTerminationDataFails(t *testing.T) { 631 eng := getTestEngine(t, time.Now()) 632 633 // given 634 proposer := vgrand.RandomStr(5) 635 id := eng.newProposalID() 636 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 637 _, binding := produceTimeTriggeredDataSourceSpec(now.Add(3 * 48 * time.Hour)) 638 settl := datasource.NewDefinition( 639 datasource.ContentTypeOracle, 640 ).SetOracleConfig( 641 &signedoracle.SpecConfiguration{ 642 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 643 Filters: []*dstypes.SpecFilter{ 644 { 645 Key: &dstypes.SpecPropertyKey{ 646 Name: "prices.ETH.value", 647 Type: datapb.PropertyKey_TYPE_INTEGER, 648 }, 649 Conditions: []*dstypes.SpecCondition{}, 650 }, 651 }, 652 }, 653 ) 654 655 riskParameters := types.UpdateMarketConfigurationLogNormal{ 656 LogNormal: &types.LogNormalRiskModel{ 657 RiskAversionParameter: num.DecimalFromFloat(0.01), 658 Tau: num.DecimalFromFloat(0.00011407711613050422), 659 Params: &types.LogNormalModelParams{ 660 Mu: num.DecimalZero(), 661 R: num.DecimalFromFloat(0.016), 662 Sigma: num.DecimalFromFloat(0.09), 663 }, 664 }, 665 } 666 proposal := types.Proposal{ 667 ID: "market-1", 668 Reference: "ref-" + id, 669 Party: proposer, 670 State: types.ProposalStateOpen, 671 Terms: &types.ProposalTerms{ 672 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 673 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 674 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 675 Change: &types.ProposalTermsUpdateMarket{ 676 UpdateMarket: &types.UpdateMarket{ 677 MarketID: vgrand.RandomStr(5), 678 Changes: &types.UpdateMarketConfiguration{ 679 Instrument: &types.UpdateInstrumentConfiguration{ 680 Code: "CRYPTO:GBPVUSD/JUN20", 681 Name: "CRYPTO:GBPVUSD/JUN20", 682 Product: &types.UpdateInstrumentConfigurationFuture{ 683 Future: &types.UpdateFutureProduct{ 684 QuoteName: "VUSD", 685 DataSourceSpecForSettlementData: *settl, 686 DataSourceSpecForTradingTermination: dsdefinition.Definition{}, 687 DataSourceSpecBinding: binding, 688 }, 689 }, 690 }, 691 RiskParameters: &riskParameters, 692 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 693 LiquiditySLAParameters: &types.LiquiditySLAParams{ 694 PriceRange: num.DecimalFromFloat(0.95), 695 CommitmentMinTimeFraction: num.NewDecimalFromFloat(0.5), 696 PerformanceHysteresisEpochs: 4, 697 SlaCompetitionFactor: num.NewDecimalFromFloat(0.5), 698 }, 699 LinearSlippageFactor: num.DecimalFromFloat(0.1), 700 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 701 LiquidationStrategy: &types.LiquidationStrategy{ 702 DisposalTimeStep: 10 * time.Second, 703 DisposalFraction: num.DecimalFromFloat(0.1), 704 FullDisposalSize: 20, 705 MaxFractionConsumed: num.DecimalFromFloat(0.01), 706 }, 707 TickSize: num.UintOne(), 708 }, 709 }, 710 }, 711 }, 712 Rationale: &types.ProposalRationale{ 713 Description: "some description", 714 }, 715 } 716 marketID := proposal.MarketUpdate().MarketID 717 718 // setup 719 eng.ensureTokenBalanceForParty(t, proposer, 1000) 720 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 721 eng.ensureExistingMarket(t, marketID) 722 eng.ensureGetMarketFuture(t, marketID) 723 724 // expect 725 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 726 727 // when 728 toSubmit, err := eng.submitProposal(t, proposal) 729 730 // then 731 assert.Error(t, err, governance.ErrMissingDataSourceSpecForTradingTermination) 732 require.Nil(t, toSubmit) 733 } 734 735 func testSubmittingProposalForMarketUpdateWithEarlyTerminationSucceeds(t *testing.T) { 736 eng := getTestEngine(t, time.Now()) 737 738 // Submit proposal. 739 // given 740 proposer := vgrand.RandomStr(5) 741 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 742 marketID := proposal.MarketUpdate().MarketID 743 744 proposal.Terms.Change.(*types.ProposalTermsUpdateMarket).UpdateMarket.Changes.Instrument.Product.(*types.UpdateInstrumentConfigurationFuture).Future.DataSourceSpecForTradingTermination.UpdateFilters( 745 []*dstypes.SpecFilter{ 746 { 747 Key: &dstypes.SpecPropertyKey{ 748 Name: "vegaprotocol.builtin.timestamp", 749 Type: datapb.PropertyKey_TYPE_TIMESTAMP, 750 }, 751 Conditions: []*dstypes.SpecCondition{ 752 { 753 Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL, 754 Value: "0", // change to internal timestamp that is in the past 755 }, 756 }, 757 }, 758 }, 759 ) 760 proposal.Terms.Change.(*types.ProposalTermsUpdateMarket).UpdateMarket.Changes.Instrument.Product.(*types.UpdateInstrumentConfigurationFuture).Future.DataSourceSpecBinding.TradingTerminationProperty = "vegaprotocol.builtin.timestamp" 761 762 // setup 763 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 764 eng.ensureExistingMarket(t, marketID) 765 eng.ensureGetMarketFuture(t, marketID) 766 eng.ensureTokenBalanceForParty(t, proposer, 1) 767 eng.ensureAllAssetEnabled(t) 768 769 // expect 770 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 771 772 // when 773 _, err := eng.submitProposal(t, proposal) 774 775 // then 776 require.NoError(t, err) 777 778 // Vote 'YES' with 10 tokens. 779 // given 780 voterWithToken1 := vgrand.RandomStr(5) 781 782 // setup 783 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 784 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0) 785 786 // expect 787 eng.expectVoteEvent(t, voterWithToken1, proposal.ID) 788 789 // when 790 err = eng.addYesVote(t, voterWithToken1, proposal.ID) 791 792 // then 793 require.NoError(t, err) 794 795 // Vote 'NO' with 2 tokens. 796 // given 797 voterWithToken2 := vgrand.RandomStr(5) 798 799 // Close the proposal. 800 // given 801 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 802 803 // setup 804 eng.ensureStakingAssetTotalSupply(t, 13) 805 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 806 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 807 808 // expect 809 eng.expectPassedProposalEvent(t, proposal.ID) 810 eng.expectVoteEvents(t) 811 eng.expectGetMarketState(t, marketID) 812 813 // when 814 eng.OnTick(context.Background(), afterClosing) 815 816 // Enact the proposal. 817 // given 818 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 819 existingMarket := types.Market{ 820 ID: marketID, 821 TradableInstrument: &types.TradableInstrument{ 822 Instrument: &types.Instrument{ 823 Name: vgrand.RandomStr(10), 824 Product: &types.InstrumentFuture{ 825 Future: &types.Future{ 826 SettlementAsset: "BTC", 827 }, 828 }, 829 }, 830 }, 831 DecimalPlaces: 3, 832 PositionDecimalPlaces: 4, 833 OpeningAuction: &types.AuctionDuration{ 834 Duration: 42, 835 }, 836 } 837 838 // setup 839 eng.ensureGetMarket(t, marketID, existingMarket) 840 841 // when 842 enacted, _ := eng.OnTick(context.Background(), afterEnactment) 843 844 // then 845 require.NotEmpty(t, enacted) 846 require.True(t, enacted[0].IsUpdateMarket()) 847 updatedMarket := enacted[0].UpdateMarket() 848 assert.Equal(t, existingMarket.ID, updatedMarket.ID) 849 assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name) 850 851 assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset) 852 assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces) 853 assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces) 854 assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration) 855 } 856 857 func testSubmittingProposalForMarketUpdateForUnknownMarketFails(t *testing.T) { 858 eng := getTestEngine(t, time.Now()) 859 860 // given 861 proposer := vgrand.RandomStr(5) 862 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 863 marketID := proposal.MarketUpdate().MarketID 864 865 // setup 866 eng.ensureTokenBalanceForParty(t, proposer, 123456789) 867 eng.ensureNonExistingMarket(t, marketID) 868 869 // expect 870 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket) 871 872 // when 873 toSubmit, err := eng.submitProposal(t, proposal) 874 875 // then 876 require.ErrorIs(t, governance.ErrMarketDoesNotExist, err) 877 require.Nil(t, toSubmit) 878 } 879 880 func testSubmittingProposalForMarketUpdateWithInternalTimeTerminationForUnknownMarketFails(t *testing.T) { 881 eng := getTestEngine(t, time.Now()) 882 883 // given 884 proposer := vgrand.RandomStr(5) 885 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false) 886 marketID := proposal.MarketUpdate().MarketID 887 888 // setup 889 eng.ensureTokenBalanceForParty(t, proposer, 123456789) 890 eng.ensureNonExistingMarket(t, marketID) 891 892 // expect 893 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidMarket) 894 895 // when 896 toSubmit, err := eng.submitProposal(t, proposal) 897 898 // then 899 require.ErrorIs(t, governance.ErrMarketDoesNotExist, err) 900 require.Nil(t, toSubmit) 901 } 902 903 func testSubmittingProposalForMarketUpdateWithInternalTimeTriggerTerminationFails(t *testing.T) { 904 eng := getTestEngine(t, time.Now()) 905 906 // given 907 proposer := vgrand.RandomStr(5) 908 id := eng.newProposalID() 909 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 910 tm := now.Add(time.Hour * 24 * 365) 911 _, binding := produceTimeTriggeredDataSourceSpec(tm) 912 settl := datasource.NewDefinition( 913 datasource.ContentTypeOracle, 914 ).SetOracleConfig( 915 &signedoracle.SpecConfiguration{ 916 Signers: []*dstypes.Signer{dstypes.CreateSignerFromString("0xDEADBEEF", dstypes.SignerTypePubKey)}, 917 Filters: []*dstypes.SpecFilter{ 918 { 919 Key: &dstypes.SpecPropertyKey{ 920 Name: "prices.ETH.value", 921 Type: datapb.PropertyKey_TYPE_INTEGER, 922 }, 923 Conditions: []*dstypes.SpecCondition{}, 924 }, 925 }, 926 }, 927 ) 928 929 term := datasource.NewDefinition( 930 datasource.ContentTypeInternalTimeTriggerTermination, 931 ).SetTimeTriggerConditionConfig( 932 []*dstypes.SpecCondition{ 933 { 934 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 935 Value: fmt.Sprintf("%d", tm.UnixNano()), 936 }, 937 }) 938 939 riskParameters := types.UpdateMarketConfigurationLogNormal{ 940 LogNormal: &types.LogNormalRiskModel{ 941 RiskAversionParameter: num.DecimalFromFloat(0.01), 942 Tau: num.DecimalFromFloat(0.00011407711613050422), 943 Params: &types.LogNormalModelParams{ 944 Mu: num.DecimalZero(), 945 R: num.DecimalFromFloat(0.016), 946 Sigma: num.DecimalFromFloat(0.09), 947 }, 948 }, 949 } 950 proposal := types.Proposal{ 951 ID: "market-1", 952 Reference: "ref-" + id, 953 Party: proposer, 954 State: types.ProposalStateOpen, 955 Terms: &types.ProposalTerms{ 956 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 957 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 958 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 959 Change: &types.ProposalTermsUpdateMarket{ 960 UpdateMarket: &types.UpdateMarket{ 961 MarketID: vgrand.RandomStr(5), 962 Changes: &types.UpdateMarketConfiguration{ 963 Instrument: &types.UpdateInstrumentConfiguration{ 964 Code: "CRYPTO:GBPVUSD/JUN20", 965 Name: "CRYPTO:GBPVUSD/JUN20", 966 Product: &types.UpdateInstrumentConfigurationFuture{ 967 Future: &types.UpdateFutureProduct{ 968 QuoteName: "VUSD", 969 DataSourceSpecForSettlementData: *settl, 970 DataSourceSpecForTradingTermination: *term, 971 DataSourceSpecBinding: binding, 972 }, 973 }, 974 }, 975 RiskParameters: &riskParameters, 976 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 977 LinearSlippageFactor: num.DecimalFromFloat(0.1), 978 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 979 LiquidationStrategy: &types.LiquidationStrategy{ 980 DisposalTimeStep: 10 * time.Second, 981 DisposalFraction: num.DecimalFromFloat(0.1), 982 FullDisposalSize: 20, 983 MaxFractionConsumed: num.DecimalFromFloat(0.01), 984 }, 985 TickSize: num.UintOne(), 986 }, 987 }, 988 }, 989 }, 990 Rationale: &types.ProposalRationale{ 991 Description: "some description", 992 }, 993 } 994 marketID := proposal.MarketUpdate().MarketID 995 996 // setup 997 eng.ensureTokenBalanceForParty(t, proposer, 1000) 998 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 999 eng.ensureExistingMarket(t, marketID) 1000 eng.ensureGetMarketFuture(t, marketID) 1001 1002 // expect 1003 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 1004 1005 // when 1006 toSubmit, err := eng.submitProposal(t, proposal) 1007 1008 // then 1009 assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed) 1010 require.Nil(t, toSubmit) 1011 } 1012 1013 func testSubmittingProposalForMarketUpdateWithInternalTimeTriggerSettlementFails(t *testing.T) { 1014 eng := getTestEngine(t, time.Now()) 1015 1016 // given 1017 proposer := vgrand.RandomStr(5) 1018 id := eng.newProposalID() 1019 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 1020 tm := now.Add(time.Hour * 24 * 365) 1021 _, binding := produceTimeTriggeredDataSourceSpec(tm) 1022 settl := datasource.NewDefinition( 1023 datasource.ContentTypeInternalTimeTriggerTermination, 1024 ).SetTimeTriggerConditionConfig( 1025 []*dstypes.SpecCondition{ 1026 { 1027 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 1028 Value: fmt.Sprintf("%d", tm.UnixNano()), 1029 }, 1030 }) 1031 1032 term := datasource.NewDefinition( 1033 datasource.ContentTypeInternalTimeTermination, 1034 ).SetTimeTriggerConditionConfig( 1035 []*dstypes.SpecCondition{ 1036 { 1037 Operator: datapb.Condition_OPERATOR_GREATER_THAN, 1038 Value: fmt.Sprintf("%d", tm.UnixNano()), 1039 }, 1040 }, 1041 ) 1042 riskParameters := types.UpdateMarketConfigurationLogNormal{ 1043 LogNormal: &types.LogNormalRiskModel{ 1044 RiskAversionParameter: num.DecimalFromFloat(0.01), 1045 Tau: num.DecimalFromFloat(0.00011407711613050422), 1046 Params: &types.LogNormalModelParams{ 1047 Mu: num.DecimalZero(), 1048 R: num.DecimalFromFloat(0.016), 1049 Sigma: num.DecimalFromFloat(0.09), 1050 }, 1051 }, 1052 } 1053 proposal := types.Proposal{ 1054 ID: "market-1", 1055 Reference: "ref-" + id, 1056 Party: proposer, 1057 State: types.ProposalStateOpen, 1058 Terms: &types.ProposalTerms{ 1059 ClosingTimestamp: now.Add(48 * time.Hour).Unix(), 1060 EnactmentTimestamp: now.Add(1 * 48 * time.Hour).Unix(), 1061 ValidationTimestamp: now.Add(1 * time.Hour).Unix(), 1062 Change: &types.ProposalTermsUpdateMarket{ 1063 UpdateMarket: &types.UpdateMarket{ 1064 MarketID: vgrand.RandomStr(5), 1065 Changes: &types.UpdateMarketConfiguration{ 1066 Instrument: &types.UpdateInstrumentConfiguration{ 1067 Code: "CRYPTO:GBPVUSD/JUN20", 1068 Name: "CRYPTO:GBPVUSD/JUN20", 1069 Product: &types.UpdateInstrumentConfigurationFuture{ 1070 Future: &types.UpdateFutureProduct{ 1071 QuoteName: "VUSD", 1072 DataSourceSpecForSettlementData: *settl, 1073 DataSourceSpecForTradingTermination: *term, 1074 DataSourceSpecBinding: binding, 1075 }, 1076 }, 1077 }, 1078 RiskParameters: &riskParameters, 1079 Metadata: []string{"asset_class:fx/crypto", "product:futures"}, 1080 LinearSlippageFactor: num.DecimalFromFloat(0.1), 1081 QuadraticSlippageFactor: num.DecimalFromFloat(0.1), 1082 LiquidationStrategy: &types.LiquidationStrategy{ 1083 DisposalTimeStep: 10 * time.Second, 1084 DisposalFraction: num.DecimalFromFloat(0.1), 1085 FullDisposalSize: 20, 1086 MaxFractionConsumed: num.DecimalFromFloat(0.01), 1087 }, 1088 TickSize: num.UintOne(), 1089 }, 1090 }, 1091 }, 1092 }, 1093 Rationale: &types.ProposalRationale{ 1094 Description: "some description", 1095 }, 1096 } 1097 marketID := proposal.MarketUpdate().MarketID 1098 1099 // setup 1100 eng.ensureTokenBalanceForParty(t, proposer, 1000) 1101 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 1102 eng.ensureExistingMarket(t, marketID) 1103 eng.ensureGetMarketFuture(t, marketID) 1104 1105 // expect 1106 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 1107 1108 // when 1109 toSubmit, err := eng.submitProposal(t, proposal) 1110 1111 // then 1112 assert.Error(t, err, governance.ErrInternalTimeTriggerForFuturesInNotAllowed) 1113 require.Nil(t, toSubmit) 1114 } 1115 1116 func testSubmittingProposalForMarketUpdateForNotEnactedMarketFails(t *testing.T) { 1117 eng := getTestEngine(t, time.Now()) 1118 1119 // given 1120 proposer := vgrand.RandomStr(5) 1121 newMarketProposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 1122 marketID := newMarketProposal.ID 1123 1124 // setup 1125 eng.ensureAllAssetEnabled(t) 1126 eng.ensureTokenBalanceForParty(t, proposer, 123456789) 1127 eng.expectOpenProposalEvent(t, proposer, marketID) 1128 1129 // when 1130 toSubmit, err := eng.submitProposal(t, newMarketProposal) 1131 1132 // then 1133 require.NoError(t, err) 1134 require.NotNil(t, toSubmit) 1135 assert.True(t, toSubmit.IsNewMarket()) 1136 1137 // given 1138 updateMarketProposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1139 updateMarketProposal.MarketUpdate().MarketID = marketID 1140 1141 // setup 1142 eng.ensureTokenBalanceForParty(t, proposer, 123456789) 1143 eng.ensureExistingMarket(t, marketID) 1144 1145 // expect 1146 eng.expectRejectedProposalEvent(t, proposer, updateMarketProposal.ID, types.ProposalErrorInvalidMarket) 1147 1148 // when 1149 toSubmit, err = eng.submitProposal(t, updateMarketProposal) 1150 1151 // then 1152 require.ErrorIs(t, governance.ErrMarketProposalStillOpen, err) 1153 require.Nil(t, toSubmit) 1154 1155 // now the original market proposal passes 1156 // given 1157 voter1 := vgrand.RandomStr(5) 1158 eng.ensureTokenBalanceForParty(t, voter1, 7) 1159 eng.expectVoteEvent(t, voter1, marketID) 1160 err = eng.addYesVote(t, voter1, marketID) 1161 require.NoError(t, err) 1162 1163 afterClosing := time.Unix(newMarketProposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1164 eng.ensureStakingAssetTotalSupply(t, 10) 1165 eng.ensureTokenBalanceForParty(t, voter1, 7) 1166 eng.expectPassedProposalEvent(t, marketID) 1167 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7") 1168 eng.expectGetMarketState(t, marketID) 1169 eng.OnTick(context.Background(), afterClosing) 1170 1171 // submitting now the market proposal has passed should work 1172 eng.ensureTokenBalanceForParty(t, proposer, 1000) 1173 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 1174 eng.ensureExistingMarket(t, marketID) 1175 eng.ensureGetMarketFuture(t, marketID) 1176 eng.expectOpenProposalEvent(t, proposer, updateMarketProposal.ID) 1177 toSubmit, err = eng.submitProposal(t, updateMarketProposal) 1178 require.NoError(t, err) 1179 require.NotNil(t, toSubmit) 1180 } 1181 1182 func testSubmittingProposalForMarketUpdateWithInsufficientEquityLikeShareFails(t *testing.T) { 1183 eng := getTestEngine(t, time.Now()) 1184 1185 // given 1186 party := vgrand.RandomStr(5) 1187 proposal := eng.newProposalForMarketUpdate("״market-1", party, eng.tsvc.GetTimeNow(), nil, nil, true) 1188 marketID := proposal.MarketUpdate().MarketID 1189 1190 // setup 1191 // eng.ensureTokenBalanceForParty(t, party, 100) 1192 eng.ensureExistingMarket(t, marketID) 1193 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.05) 1194 1195 // expect 1196 eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientTokens) 1197 1198 // when 1199 toSubmit, err := eng.submitProposal(t, proposal) 1200 1201 // then 1202 require.Error(t, err) 1203 assert.Contains(t, err.Error(), "no balance for party") 1204 require.Nil(t, toSubmit) 1205 } 1206 1207 func testPreEnactmentOfMarketUpdateSucceeds(t *testing.T) { 1208 eng := getTestEngine(t, time.Now()) 1209 1210 // Submit proposal. 1211 // given 1212 proposer := vgrand.RandomStr(5) 1213 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1214 marketID := proposal.MarketUpdate().MarketID 1215 1216 // setup 1217 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1218 eng.ensureExistingMarket(t, marketID) 1219 eng.ensureGetMarketFuture(t, marketID) 1220 eng.ensureTokenBalanceForParty(t, proposer, 1) 1221 eng.ensureAllAssetEnabled(t) 1222 1223 // expect 1224 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1225 1226 // when 1227 _, err := eng.submitProposal(t, proposal) 1228 1229 // then 1230 require.NoError(t, err) 1231 1232 // Vote 'YES' with 10 tokens. 1233 // given 1234 voterWithToken1 := vgrand.RandomStr(5) 1235 1236 // setup 1237 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1238 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0) 1239 1240 // expect 1241 eng.expectVoteEvent(t, voterWithToken1, proposal.ID) 1242 1243 // when 1244 err = eng.addYesVote(t, voterWithToken1, proposal.ID) 1245 1246 // then 1247 require.NoError(t, err) 1248 1249 // Vote 'NO' with 2 tokens. 1250 // given 1251 voterWithToken2 := vgrand.RandomStr(5) 1252 1253 // setup 1254 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1255 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0) 1256 1257 // expect 1258 eng.expectVoteEvent(t, voterWithToken2, proposal.ID) 1259 1260 // then 1261 err = eng.addNoVote(t, voterWithToken2, proposal.ID) 1262 1263 // then 1264 require.NoError(t, err) 1265 1266 // Close the proposal. 1267 // given 1268 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1269 1270 // setup 1271 eng.ensureStakingAssetTotalSupply(t, 13) 1272 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1273 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1274 1275 // expect 1276 eng.expectPassedProposalEvent(t, proposal.ID) 1277 eng.expectVoteEvents(t) 1278 eng.expectGetMarketState(t, marketID) 1279 1280 // when 1281 eng.OnTick(context.Background(), afterClosing) 1282 1283 // Enact the proposal. 1284 // given 1285 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1286 existingMarket := types.Market{ 1287 ID: marketID, 1288 TradableInstrument: &types.TradableInstrument{ 1289 Instrument: &types.Instrument{ 1290 Name: vgrand.RandomStr(10), 1291 Product: &types.InstrumentFuture{ 1292 Future: &types.Future{ 1293 SettlementAsset: "BTC", 1294 }, 1295 }, 1296 }, 1297 }, 1298 DecimalPlaces: 3, 1299 PositionDecimalPlaces: 4, 1300 OpeningAuction: &types.AuctionDuration{ 1301 Duration: 42, 1302 }, 1303 } 1304 1305 // setup 1306 eng.ensureGetMarket(t, marketID, existingMarket) 1307 1308 // when 1309 enacted, _ := eng.OnTick(context.Background(), afterEnactment) 1310 1311 // then 1312 require.NotEmpty(t, enacted) 1313 require.True(t, enacted[0].IsUpdateMarket()) 1314 updatedMarket := enacted[0].UpdateMarket() 1315 assert.Equal(t, existingMarket.ID, updatedMarket.ID) 1316 assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name) 1317 assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset) 1318 assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces) 1319 assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces) 1320 assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration) 1321 } 1322 1323 func testPreEnactmentOfMarketUpdateWithInternalTimeTerminationSucceeds(t *testing.T) { 1324 eng := getTestEngine(t, time.Now()) 1325 1326 // Submit proposal. 1327 // given 1328 proposer := vgrand.RandomStr(5) 1329 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, false) 1330 marketID := proposal.MarketUpdate().MarketID 1331 1332 // setup 1333 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1334 eng.ensureExistingMarket(t, marketID) 1335 eng.ensureGetMarketFuture(t, marketID) 1336 eng.ensureTokenBalanceForParty(t, proposer, 1) 1337 eng.ensureAllAssetEnabled(t) 1338 1339 // expect 1340 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1341 1342 // when 1343 _, err := eng.submitProposal(t, proposal) 1344 1345 // then 1346 require.NoError(t, err) 1347 1348 // Vote 'YES' with 10 tokens. 1349 // given 1350 voterWithToken1 := vgrand.RandomStr(5) 1351 1352 // setup 1353 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1354 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0) 1355 1356 // expect 1357 eng.expectVoteEvent(t, voterWithToken1, proposal.ID) 1358 1359 // when 1360 err = eng.addYesVote(t, voterWithToken1, proposal.ID) 1361 1362 // then 1363 require.NoError(t, err) 1364 1365 // Vote 'NO' with 2 tokens. 1366 // given 1367 voterWithToken2 := vgrand.RandomStr(5) 1368 1369 // setup 1370 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1371 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0) 1372 1373 // expect 1374 eng.expectVoteEvent(t, voterWithToken2, proposal.ID) 1375 1376 // then 1377 err = eng.addNoVote(t, voterWithToken2, proposal.ID) 1378 1379 // then 1380 require.NoError(t, err) 1381 1382 // Close the proposal. 1383 // given 1384 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1385 1386 // setup 1387 eng.ensureStakingAssetTotalSupply(t, 13) 1388 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1389 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1390 1391 // expect 1392 eng.expectPassedProposalEvent(t, proposal.ID) 1393 eng.expectVoteEvents(t) 1394 eng.expectGetMarketState(t, marketID) 1395 1396 // when 1397 eng.OnTick(context.Background(), afterClosing) 1398 1399 // Enact the proposal. 1400 // given 1401 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 1402 existingMarket := types.Market{ 1403 ID: marketID, 1404 TradableInstrument: &types.TradableInstrument{ 1405 Instrument: &types.Instrument{ 1406 Name: vgrand.RandomStr(10), 1407 Product: &types.InstrumentFuture{ 1408 Future: &types.Future{ 1409 SettlementAsset: "BTC", 1410 }, 1411 }, 1412 }, 1413 }, 1414 DecimalPlaces: 3, 1415 PositionDecimalPlaces: 4, 1416 OpeningAuction: &types.AuctionDuration{ 1417 Duration: 42, 1418 }, 1419 } 1420 1421 // setup 1422 eng.ensureGetMarket(t, marketID, existingMarket) 1423 1424 // when 1425 enacted, _ := eng.OnTick(context.Background(), afterEnactment) 1426 1427 // then 1428 require.NotEmpty(t, enacted) 1429 require.True(t, enacted[0].IsUpdateMarket()) 1430 updatedMarket := enacted[0].UpdateMarket() 1431 assert.Equal(t, existingMarket.ID, updatedMarket.ID) 1432 assert.Equal(t, "UPDATED_MARKET_NAME", updatedMarket.TradableInstrument.Instrument.Name) 1433 assert.Equal(t, existingMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset, updatedMarket.TradableInstrument.Instrument.Product.(*types.InstrumentFuture).Future.SettlementAsset) 1434 assert.Equal(t, existingMarket.DecimalPlaces, updatedMarket.DecimalPlaces) 1435 assert.Equal(t, existingMarket.PositionDecimalPlaces, updatedMarket.PositionDecimalPlaces) 1436 assert.Equal(t, existingMarket.OpeningAuction.Duration, updatedMarket.OpeningAuction.Duration) 1437 } 1438 1439 func testRejectingProposalForMarketUpdateSucceeds(t *testing.T) { 1440 eng := getTestEngine(t, time.Now()) 1441 1442 // given 1443 party := vgrand.RandomStr(5) 1444 proposal := eng.newProposalForMarketUpdate("market-1", party, eng.tsvc.GetTimeNow(), nil, nil, true) 1445 marketID := proposal.MarketUpdate().MarketID 1446 1447 // setup 1448 eng.ensureAllAssetEnabled(t) 1449 eng.ensureExistingMarket(t, marketID) 1450 eng.ensureGetMarketFuture(t, marketID) 1451 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.7) 1452 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketMinProposerEquityLikeShare, "0.1") 1453 eng.ensureTokenBalanceForParty(t, party, 10000) 1454 1455 // expect 1456 eng.expectOpenProposalEvent(t, party, proposal.ID) 1457 1458 // when 1459 toSubmit, err := eng.submitProposal(t, proposal) 1460 1461 // then 1462 require.NoError(t, err) 1463 require.NotNil(t, toSubmit) 1464 1465 // expect 1466 eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorCouldNotInstantiateMarket) 1467 1468 // when 1469 err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError) 1470 1471 // then 1472 require.NoError(t, err) 1473 1474 // when 1475 // Just one more time to make sure it was removed from proposals. 1476 err = eng.RejectProposal(context.Background(), toSubmit.Proposal(), types.ProposalErrorCouldNotInstantiateMarket, assert.AnError) 1477 1478 // then 1479 assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error()) 1480 } 1481 1482 func testVotingWithoutMinimumTokenHoldersAndEquityLikeShareMakesMarketUpdateProposalPassed(t *testing.T) { 1483 eng := getTestEngine(t, time.Now()) 1484 1485 // Submit proposal. 1486 // given 1487 proposer := vgrand.RandomStr(5) 1488 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1489 marketID := proposal.MarketUpdate().MarketID 1490 1491 // setup 1492 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5") 1493 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipationLP, "0.5") 1494 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 1495 eng.ensureExistingMarket(t, marketID) 1496 eng.ensureGetMarketFuture(t, marketID) 1497 eng.ensureTokenBalanceForParty(t, proposer, 1) 1498 eng.ensureAllAssetEnabled(t) 1499 1500 // expect 1501 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1502 1503 // when 1504 _, err := eng.submitProposal(t, proposal) 1505 1506 // then 1507 require.NoError(t, err) 1508 1509 // Vote using a token holder without equity-like share. 1510 // when 1511 voterWithToken := vgrand.RandomStr(5) 1512 1513 // setup 1514 eng.ensureTokenBalanceForParty(t, voterWithToken, 1) 1515 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1516 1517 // expect 1518 eng.expectVoteEvent(t, voterWithToken, proposal.ID) 1519 1520 // when 1521 err = eng.addYesVote(t, voterWithToken, proposal.ID) 1522 1523 // then 1524 require.NoError(t, err) 1525 1526 // Vote using equity-like share holder without tokens. 1527 // given 1528 voterWithELS := vgrand.RandomStr(5) 1529 1530 // setup 1531 eng.ensureTokenBalanceForParty(t, voterWithELS, 0) 1532 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1) 1533 1534 // expect 1535 eng.expectVoteEvent(t, voterWithELS, proposal.ID) 1536 1537 // when 1538 err = eng.addNoVote(t, voterWithELS, proposal.ID) 1539 1540 // then 1541 require.NoError(t, err) 1542 1543 // Closing the proposal. 1544 // given 1545 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1546 1547 // setup 1548 eng.ensureStakingAssetTotalSupply(t, 10) 1549 eng.ensureTokenBalanceForParty(t, voterWithToken, 1) 1550 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1551 eng.ensureTokenBalanceForParty(t, voterWithELS, 0) 1552 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS, 0.1) 1553 1554 // expect 1555 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached) 1556 eng.expectVoteEvents(t) 1557 eng.expectGetMarketState(t, proposal.ID) 1558 1559 // when 1560 eng.OnTick(context.Background(), afterClosing) 1561 } 1562 1563 func testVotingWithMajorityOfYesFromTokenHoldersMakesMarketUpdateProposalPassed(t *testing.T) { 1564 eng := getTestEngine(t, time.Now()) 1565 1566 // Submit proposal. 1567 // given 1568 proposer := vgrand.RandomStr(5) 1569 proposal := eng.newProposalForMarketUpdate("״market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1570 marketID := proposal.MarketUpdate().MarketID 1571 1572 // setup 1573 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1574 eng.ensureExistingMarket(t, marketID) 1575 eng.ensureGetMarketFuture(t, marketID) 1576 eng.ensureTokenBalanceForParty(t, proposer, 1) 1577 eng.ensureAllAssetEnabled(t) 1578 1579 // expect 1580 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1581 1582 // when 1583 _, err := eng.submitProposal(t, proposal) 1584 1585 // then 1586 require.NoError(t, err) 1587 1588 // Vote 'YES' with 10 tokens. 1589 // given 1590 voterWithToken1 := vgrand.RandomStr(5) 1591 1592 // setup 1593 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1594 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0) 1595 1596 // expect 1597 eng.expectVoteEvent(t, voterWithToken1, proposal.ID) 1598 1599 // when 1600 err = eng.addYesVote(t, voterWithToken1, proposal.ID) 1601 1602 // then 1603 require.NoError(t, err) 1604 1605 // Vote 'NO' with 2 tokens. 1606 // given 1607 voterWithToken2 := vgrand.RandomStr(5) 1608 1609 // setup 1610 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1611 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0) 1612 1613 // expect 1614 eng.expectVoteEvent(t, voterWithToken2, proposal.ID) 1615 1616 // then 1617 err = eng.addNoVote(t, voterWithToken2, proposal.ID) 1618 1619 // then 1620 require.NoError(t, err) 1621 1622 // Vote 'NO' with 0.1 of equity-like share. 1623 // given 1624 voterWithELS1 := vgrand.RandomStr(5) 1625 1626 // setup 1627 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1628 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1629 1630 // expect 1631 eng.expectVoteEvent(t, voterWithELS1, proposal.ID) 1632 1633 // when 1634 err = eng.addNoVote(t, voterWithELS1, proposal.ID) 1635 1636 // then 1637 require.NoError(t, err) 1638 1639 // Vote 'NO' with 0.5 of equity-like share. 1640 // given 1641 voterWithELS2 := vgrand.RandomStr(5) 1642 1643 // setup 1644 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1645 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1646 1647 // expect 1648 eng.expectVoteEvent(t, voterWithELS2, proposal.ID) 1649 1650 // when 1651 err = eng.addNoVote(t, voterWithELS2, proposal.ID) 1652 1653 // then 1654 require.NoError(t, err) 1655 1656 // Close the proposal. 1657 // given 1658 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1659 1660 // setup 1661 eng.ensureStakingAssetTotalSupply(t, 13) 1662 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1663 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1664 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1665 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1666 1667 // expect 1668 eng.expectPassedProposalEvent(t, proposal.ID) 1669 eng.expectVoteEvents(t) 1670 eng.expectGetMarketState(t, proposal.ID) 1671 1672 // when 1673 eng.OnTick(context.Background(), afterClosing) 1674 } 1675 1676 func testVotingWithMajorityOfNoFromTokenHoldersMakesMarketUpdateProposalDeclined(t *testing.T) { 1677 eng := getTestEngine(t, time.Now()) 1678 1679 // Submit proposal. 1680 // given 1681 proposer := vgrand.RandomStr(5) 1682 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1683 marketID := proposal.MarketUpdate().MarketID 1684 1685 // setup 1686 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1687 eng.ensureExistingMarket(t, marketID) 1688 eng.ensureGetMarketFuture(t, marketID) 1689 eng.ensureTokenBalanceForParty(t, proposer, 1) 1690 eng.ensureAllAssetEnabled(t) 1691 1692 // expect 1693 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1694 1695 // when 1696 _, err := eng.submitProposal(t, proposal) 1697 1698 // then 1699 require.NoError(t, err) 1700 1701 // Vote 'NO' with 10 tokens. 1702 // given 1703 voterWithToken1 := vgrand.RandomStr(5) 1704 1705 // setup 1706 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1707 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken1, 0) 1708 1709 // expect 1710 eng.expectVoteEvent(t, voterWithToken1, proposal.ID) 1711 1712 // when 1713 err = eng.addNoVote(t, voterWithToken1, proposal.ID) 1714 1715 // then 1716 require.NoError(t, err) 1717 1718 // Vote 'YES' with 2 tokens. 1719 // given 1720 voterWithToken2 := vgrand.RandomStr(5) 1721 1722 // setup 1723 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1724 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken2, 0) 1725 1726 // expect 1727 eng.expectVoteEvent(t, voterWithToken2, proposal.ID) 1728 1729 // then 1730 err = eng.addYesVote(t, voterWithToken2, proposal.ID) 1731 1732 // then 1733 require.NoError(t, err) 1734 1735 // Vote 'YES' with 0.1 of equity-like share. 1736 // given 1737 voterWithELS1 := vgrand.RandomStr(5) 1738 1739 // setup 1740 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1741 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1742 1743 // expect 1744 eng.expectVoteEvent(t, voterWithELS1, proposal.ID) 1745 1746 // when 1747 err = eng.addYesVote(t, voterWithELS1, proposal.ID) 1748 1749 // then 1750 require.NoError(t, err) 1751 1752 // Vote 'YES' with 0.5 of equity-like share. 1753 // given 1754 voterWithELS2 := vgrand.RandomStr(5) 1755 1756 // setup 1757 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1758 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1759 1760 // expect 1761 eng.expectVoteEvent(t, voterWithELS2, proposal.ID) 1762 1763 // when 1764 err = eng.addYesVote(t, voterWithELS2, proposal.ID) 1765 1766 // then 1767 require.NoError(t, err) 1768 1769 // Close the proposal. 1770 // given 1771 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1772 1773 // setup 1774 eng.ensureStakingAssetTotalSupply(t, 13) 1775 eng.ensureTokenBalanceForParty(t, voterWithToken1, 10) 1776 eng.ensureTokenBalanceForParty(t, voterWithToken2, 2) 1777 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1778 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1779 1780 // expect 1781 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached) 1782 eng.expectVoteEvents(t) 1783 eng.expectGetMarketState(t, proposal.ID) 1784 1785 // when 1786 eng.OnTick(context.Background(), afterClosing) 1787 } 1788 1789 func testVotingWithoutTokenAndMajorityOfYesFromEquityLikeShareHoldersMakesMarketUpdateProposalPassed(t *testing.T) { 1790 eng := getTestEngine(t, time.Now()) 1791 1792 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5") 1793 1794 // Submit proposal. 1795 // given 1796 proposer := vgrand.RandomStr(5) 1797 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1798 marketID := proposal.MarketUpdate().MarketID 1799 1800 // setup 1801 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1802 eng.ensureExistingMarket(t, marketID) 1803 eng.ensureGetMarketFuture(t, marketID) 1804 eng.ensureTokenBalanceForParty(t, proposer, 1) 1805 eng.ensureAllAssetEnabled(t) 1806 1807 // expect 1808 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1809 1810 // when 1811 _, err := eng.submitProposal(t, proposal) 1812 1813 // then 1814 require.NoError(t, err) 1815 1816 // Vote 'NO' with 2 tokens. 1817 // given 1818 voterWithToken := vgrand.RandomStr(5) 1819 1820 // setup 1821 eng.ensureTokenBalanceForParty(t, voterWithToken, 2) 1822 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1823 1824 // expect 1825 eng.expectVoteEvent(t, voterWithToken, proposal.ID) 1826 1827 // when 1828 err = eng.addNoVote(t, voterWithToken, proposal.ID) 1829 1830 // then 1831 require.NoError(t, err) 1832 1833 // Vote 'NO' with 0.1 of equity-like share. 1834 // given 1835 voterWithELS1 := vgrand.RandomStr(5) 1836 1837 // setup 1838 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1839 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1840 1841 // expect 1842 eng.expectVoteEvent(t, voterWithELS1, proposal.ID) 1843 1844 // when 1845 err = eng.addNoVote(t, voterWithELS1, proposal.ID) 1846 1847 // then 1848 require.NoError(t, err) 1849 1850 // Vote 'YES' with 0.5 of equity-like share. 1851 // given 1852 voterWithELS2 := vgrand.RandomStr(5) 1853 1854 // setup 1855 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1856 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1857 1858 // expect 1859 eng.expectVoteEvent(t, voterWithELS2, proposal.ID) 1860 1861 // when 1862 err = eng.addYesVote(t, voterWithELS2, proposal.ID) 1863 1864 // then 1865 require.NoError(t, err) 1866 1867 // Close the proposal. 1868 // given 1869 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1870 1871 // setup 1872 eng.ensureStakingAssetTotalSupply(t, 13) 1873 eng.ensureTokenBalanceForParty(t, voterWithToken, 2) 1874 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1875 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1876 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1877 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1878 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1879 1880 // expect 1881 eng.expectPassedProposalEvent(t, proposal.ID) 1882 eng.expectVoteEvents(t) 1883 eng.expectGetMarketState(t, proposal.ID) 1884 1885 // when 1886 eng.OnTick(context.Background(), afterClosing) 1887 } 1888 1889 func testVotingWithoutTokenAndMajorityOfNoFromEquityLikeShareHoldersMakesMarketUpdateProposalDeclined(t *testing.T) { 1890 eng := getTestEngine(t, time.Now()) 1891 1892 // Submit proposal. 1893 // given 1894 1895 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0.5") 1896 1897 proposer := vgrand.RandomStr(5) 1898 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1899 marketID := proposal.MarketUpdate().MarketID 1900 1901 // setup 1902 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.7) 1903 eng.ensureExistingMarket(t, marketID) 1904 eng.ensureGetMarketFuture(t, marketID) 1905 eng.ensureTokenBalanceForParty(t, proposer, 1) 1906 eng.ensureAllAssetEnabled(t) 1907 1908 // expect 1909 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 1910 1911 // when 1912 _, err := eng.submitProposal(t, proposal) 1913 1914 // then 1915 require.NoError(t, err) 1916 1917 // Vote 'YES' with 2 tokens. 1918 // given 1919 voterWithToken := vgrand.RandomStr(5) 1920 1921 // setup 1922 eng.ensureTokenBalanceForParty(t, voterWithToken, 2) 1923 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1924 1925 // expect 1926 eng.expectVoteEvent(t, voterWithToken, proposal.ID) 1927 1928 // when 1929 err = eng.addYesVote(t, voterWithToken, proposal.ID) 1930 1931 // then 1932 require.NoError(t, err) 1933 1934 // Vote 'YES' with 0.1 of equity-like share. 1935 // given 1936 voterWithELS1 := vgrand.RandomStr(5) 1937 1938 // setup 1939 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1940 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1941 1942 // expect 1943 eng.expectVoteEvent(t, voterWithELS1, proposal.ID) 1944 1945 // when 1946 err = eng.addYesVote(t, voterWithELS1, proposal.ID) 1947 1948 // then 1949 require.NoError(t, err) 1950 1951 // Vote 'NO' with 0.5 of equity-like share. 1952 // given 1953 voterWithELS2 := vgrand.RandomStr(5) 1954 1955 // setup 1956 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1957 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1958 1959 // expect 1960 eng.expectVoteEvent(t, voterWithELS2, proposal.ID) 1961 1962 // when 1963 err = eng.addNoVote(t, voterWithELS2, proposal.ID) 1964 1965 // then 1966 require.NoError(t, err) 1967 1968 // Close the proposal. 1969 // given 1970 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 1971 1972 // setup 1973 eng.ensureStakingAssetTotalSupply(t, 13) 1974 eng.ensureTokenBalanceForParty(t, voterWithToken, 2) 1975 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithToken, 0) 1976 eng.ensureTokenBalanceForParty(t, voterWithELS1, 0) 1977 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS1, 0.1) 1978 eng.ensureTokenBalanceForParty(t, voterWithELS2, 0) 1979 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, voterWithELS2, 0.7) 1980 1981 // ensure setting again the values have no effect 1982 eng.ensureNetworkParameter(t, netparams.GovernanceProposalUpdateMarketRequiredParticipation, "0") 1983 1984 // expect 1985 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorMajorityThresholdNotReached) 1986 eng.expectVoteEvents(t) 1987 eng.expectGetMarketState(t, proposal.ID) 1988 1989 // when 1990 eng.OnTick(context.Background(), afterClosing) 1991 } 1992 1993 func TestSubmitProposalWithInconsistentProductFails(t *testing.T) { 1994 eng := getTestEngine(t, time.Now()) 1995 1996 // given 1997 proposer := vgrand.RandomStr(5) 1998 proposal := eng.newProposalForMarketUpdate("market-1", proposer, eng.tsvc.GetTimeNow(), nil, nil, true) 1999 marketID := proposal.MarketUpdate().MarketID 2000 2001 // setup 2002 eng.ensureTokenBalanceForParty(t, proposer, 1000) 2003 eng.ensureEquityLikeShareForMarketAndParty(t, marketID, proposer, 0.1) 2004 2005 // setup market will be a perpetual being updated to a future 2006 eng.ensureExistingMarket(t, marketID) 2007 eng.ensureGetMarketPerpetual(t, marketID) 2008 2009 // expect 2010 eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidFutureProduct) 2011 2012 // when 2013 toSubmit, err := eng.submitProposal(t, proposal) 2014 2015 // then 2016 require.ErrorIs(t, err, governance.ErrUpdateMarketDifferentProduct) 2017 require.Nil(t, toSubmit) 2018 }