From 6ebe5c79ec4a0aedcf6eefb3b7762f3baa945154 Mon Sep 17 00:00:00 2001 From: Jeremy Letang Date: Thu, 29 Jun 2023 14:18:29 +0200 Subject: [PATCH] feat: allow update market proposal submission with only ELS Signed-off-by: Jeremy Letang --- CHANGELOG.md | 1 + core/governance/engine.go | 42 ++++++---- core/governance/engine_test.go | 84 +++++++++++++++---- core/governance/engine_update_market_test.go | 6 +- .../engine_update_spot_market_test.go | 6 +- 5 files changed, 101 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e71c56bb72..f72aa16cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - [8508](https://github.com/vegaprotocol/vega/issues/8508) - Add network parameters for SLA. - [8468](https://github.com/vegaprotocol/vega/issues/8468) - Wire in stop orders in markets - [8528](https://github.com/vegaprotocol/vega/issues/8528) - Add support for Stop Orders in the data node. +- [8635](https://github.com/vegaprotocol/vega/issues/8635) - Allow market update proposal with ELS only ### 🐛 Fixes diff --git a/core/governance/engine.go b/core/governance/engine.go index 5555ecf724..3058938aaa 100644 --- a/core/governance/engine.go +++ b/core/governance/engine.go @@ -792,35 +792,41 @@ func (e *Engine) validateOpenProposal(proposal *types.Proposal) (types.ProposalE fmt.Errorf("proposal enactment time cannot be before closing time, expected > %v got %v", closeTime.UTC(), enactTime.UTC()) } - proposerTokens, err := getGovernanceTokens(e.accs, proposal.Party) - if err != nil { - e.log.Debug("proposer have no governance token", - logging.PartyID(proposal.Party), - logging.ProposalID(proposal.ID)) - return types.ProposalErrorInsufficientTokens, err - } - if proposerTokens.LT(params.MinProposerBalance) { - e.log.Debug("proposer have insufficient governance token", - logging.BigUint("expect-balance", params.MinProposerBalance), - logging.String("proposer-balance", proposerTokens.String()), - logging.PartyID(proposal.Party), - logging.ProposalID(proposal.ID)) - return types.ProposalErrorInsufficientTokens, - fmt.Errorf("proposer have insufficient governance token, expected >= %v got %v", params.MinProposerBalance, proposerTokens) - } + checkProposerToken := true if proposal.IsMarketUpdate() { proposalError, err := e.validateMarketUpdate(proposal, params) - if err != nil { + if err != nil && proposalError != types.ProposalErrorInsufficientEquityLikeShare { return proposalError, err } + checkProposerToken = proposalError == types.ProposalErrorInsufficientEquityLikeShare } if proposal.IsSpotMarketUpdate() { proposalError, err := e.validateSpotMarketUpdate(proposal, params) - if err != nil { + if err != nil && proposalError != types.ProposalErrorInsufficientEquityLikeShare { return proposalError, err } + checkProposerToken = proposalError == types.ProposalErrorInsufficientEquityLikeShare + } + + if checkProposerToken { + proposerTokens, err := getGovernanceTokens(e.accs, proposal.Party) + if err != nil { + e.log.Debug("proposer have no governance token", + logging.PartyID(proposal.Party), + logging.ProposalID(proposal.ID)) + return types.ProposalErrorInsufficientTokens, err + } + if proposerTokens.LT(params.MinProposerBalance) { + e.log.Debug("proposer have insufficient governance token", + logging.BigUint("expect-balance", params.MinProposerBalance), + logging.String("proposer-balance", proposerTokens.String()), + logging.PartyID(proposal.Party), + logging.ProposalID(proposal.ID)) + return types.ProposalErrorInsufficientTokens, + fmt.Errorf("proposer have insufficient governance token, expected >= %v got %v", params.MinProposerBalance, proposerTokens) + } } return e.validateChange(proposal.Terms) diff --git a/core/governance/engine_test.go b/core/governance/engine_test.go index cf3263ea86..4eb6de074f 100644 --- a/core/governance/engine_test.go +++ b/core/governance/engine_test.go @@ -67,6 +67,7 @@ func TestSubmitProposals(t *testing.T) { t.Run("Submitting a proposal with non-existing account fails", testSubmittingProposalWithNonExistingAccountFails) t.Run("Submitting a proposal with internal time termination with non-existing account fails", testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails) t.Run("Submitting a proposal without enough stake fails", testSubmittingProposalWithoutEnoughStakeFails) + t.Run("Submitting an update market proposal without enough stake and els fails", testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails) t.Run("Submitting a proposal with internal time termination without enough stake fails", testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails) t.Run("Submitting a time-triggered proposal for new market with termination time before enactment time fails", testSumittingTimeTriggeredProposalNewMarketTerminationBeforeEnactmentFails) @@ -82,6 +83,7 @@ func TestSubmitProposals(t *testing.T) { t.Run("Updating voters key on votes with internal time termination succeeds", testUpdatingVotersKeyOnVotesWithInternalTimeTerminationSucceeds) t.Run("Computing the governance state hash is deterministic", testComputingGovernanceStateHashIsDeterministic) + t.Run("Submit proposal update market", testSubmitProposalMarketUpdate) } func testUpdatingVotersKeyOnVotesSucceeds(t *testing.T) { @@ -239,9 +241,6 @@ func testSubmittingProposalWithNonExistingAccountFails(t *testing.T) { { name: "For new market", proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), - }, { - name: "For market update", - proposal: eng.newProposalForMarketUpdate("", party, now, nil, nil, true), }, { name: "For new asset", proposal: eng.newProposalForNewAsset(party, now), @@ -268,6 +267,38 @@ func testSubmittingProposalWithNonExistingAccountFails(t *testing.T) { } } +func testSubmitProposalMarketUpdate(t *testing.T) { + eng := getTestEngine(t) + defer eng.ctrl.Finish() + + // given + party := vgrand.RandomStr(5) + now := eng.tsvc.GetTimeNow() + tc := struct { + name string + proposal types.Proposal + }{ + name: "For market update", + proposal: eng.newProposalForMarketUpdate("", party, now, nil, nil, true), + } + + // test that with no account but equity like share, a market update proposal goes through + t.Run(tc.name, func(tt *testing.T) { + // setup + eng.ensureAllAssetEnabled(tt) + eng.ensureNoAccountForParty(tt, party) + eng.expectOpenProposalEvent(tt, party, tc.proposal.ID) + + eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true) + eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalOne(), true) + // when + _, err := eng.submitProposal(tt, tc.proposal) + + // then + require.NoError(tt, err) + }) +} + func testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFails(t *testing.T) { eng := getTestEngine(t) defer eng.ctrl.Finish() @@ -283,9 +314,6 @@ func testSubmittingProposalWithInternalTimeTerminationWithNonExistingAccountFail { name: "For new market", proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), - }, { - name: "For market update", - proposal: eng.newProposalForMarketUpdate("", party, now, nil, nil, false), }, { name: "For new asset", proposal: eng.newProposalForNewAsset(party, now), @@ -329,10 +357,6 @@ func testSubmittingProposalWithoutEnoughStakeFails(t *testing.T) { name: "For new market", minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance, proposal: eng.newProposalForNewMarket(party, now, nil, nil, true), - }, { - name: "For market update", - minProposerBalanceParam: netparams.GovernanceProposalUpdateMarketMinProposerBalance, - proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), }, { name: "For new asset", minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance, @@ -362,6 +386,42 @@ func testSubmittingProposalWithoutEnoughStakeFails(t *testing.T) { } } +func testSubmittingUpdateMarketProposalWithoutEnoughStakeAndELSFails(t *testing.T) { + eng := getTestEngine(t) + defer eng.ctrl.Finish() + + // given + party := vgrand.RandomStr(5) + now := eng.tsvc.GetTimeNow() + + tc := struct { + name string + minProposerBalanceParam string + proposal types.Proposal + }{ + name: "For market update", + minProposerBalanceParam: netparams.GovernanceProposalUpdateMarketMinProposerBalance, + proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, true), + } + + t.Run(tc.name, func(tt *testing.T) { + // setup + eng.ensureTokenBalanceForParty(tt, party, 10) + eng.ensureNetworkParameter(tt, tc.minProposerBalanceParam, "10000") + eng.ensureAllAssetEnabled(tt) + eng.expectRejectedProposalEvent(tt, party, tc.proposal.ID, types.ProposalErrorInsufficientTokens) + + // when + eng.markets.EXPECT().MarketExists(gomock.Any()).Return(true) + eng.markets.EXPECT().GetEquityLikeShareForMarketAndParty(gomock.Any(), gomock.Any()).Return(num.DecimalZero(), true) + _, err := eng.submitProposal(tt, tc.proposal) + + // then + require.Error(tt, err) + assert.Contains(t, err.Error(), "proposer have insufficient governance token, expected >=") + }) +} + func testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails(t *testing.T) { eng := getTestEngine(t) defer eng.ctrl.Finish() @@ -379,10 +439,6 @@ func testSubmittingProposalWithInternalTimeTerminationWithoutEnoughStakeFails(t name: "For new market", minProposerBalanceParam: netparams.GovernanceProposalMarketMinProposerBalance, proposal: eng.newProposalForNewMarket(party, now, nil, nil, false), - }, { - name: "For market update", - minProposerBalanceParam: netparams.GovernanceProposalUpdateMarketMinProposerBalance, - proposal: eng.newProposalForMarketUpdate("market-1", party, now, nil, nil, false), }, { name: "For new asset", minProposerBalanceParam: netparams.GovernanceProposalAssetMinProposerBalance, diff --git a/core/governance/engine_update_market_test.go b/core/governance/engine_update_market_test.go index 72e10133b2..fe196fdd8e 100644 --- a/core/governance/engine_update_market_test.go +++ b/core/governance/engine_update_market_test.go @@ -622,19 +622,19 @@ func testSubmittingProposalForMarketUpdateWithInsufficientEquityLikeShareFails(t marketID := proposal.MarketUpdate().MarketID // setup - eng.ensureTokenBalanceForParty(t, party, 100) + // eng.ensureTokenBalanceForParty(t, party, 100) eng.ensureExistingMarket(t, marketID) eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.05) // expect - eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientEquityLikeShare) + eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientTokens) // when toSubmit, err := eng.submitProposal(t, proposal) // then require.Error(t, err) - assert.Contains(t, err.Error(), "proposer have insufficient equity-like share, expected >=") + assert.Contains(t, err.Error(), "no balance for party") require.Nil(t, toSubmit) } diff --git a/core/governance/engine_update_spot_market_test.go b/core/governance/engine_update_spot_market_test.go index b8521cfe39..2dbbeb4adf 100644 --- a/core/governance/engine_update_spot_market_test.go +++ b/core/governance/engine_update_spot_market_test.go @@ -142,19 +142,19 @@ func testSubmittingProposalForSpotMarketUpdateWithInsufficientEquityLikeShareFai marketID := proposal.MarketUpdate().MarketID // setup - eng.ensureTokenBalanceForParty(t, party, 100) + // eng.ensureTokenBalanceForParty(t, party, 100) eng.ensureExistingMarket(t, marketID) eng.ensureEquityLikeShareForMarketAndParty(t, marketID, party, 0.05) // expect - eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientEquityLikeShare) + eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorInsufficientTokens) // when toSubmit, err := eng.submitProposal(t, proposal) // then require.Error(t, err) - assert.Contains(t, err.Error(), "proposer have insufficient equity-like share, expected >=") + assert.Contains(t, err.Error(), "no balance for party") require.Nil(t, toSubmit) }