code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_batch_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/events" 25 "code.vegaprotocol.io/vega/core/governance" 26 "code.vegaprotocol.io/vega/core/netparams" 27 "code.vegaprotocol.io/vega/core/types" 28 "code.vegaprotocol.io/vega/libs/num" 29 vgrand "code.vegaprotocol.io/vega/libs/rand" 30 31 "github.com/golang/mock/gomock" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func TestSubmitBatchProposals(t *testing.T) { 37 t.Run("Submitted batch proposal is declined", testSubmittingBatchProposalDeclined) 38 t.Run("Submitted batch proposal has passed", testSubmittingBatchProposalPassed) 39 t.Run("Submitting batch fails if any of the terms fails validation", testSubmittingBatchProposalFailsWhenTermValidationFails) 40 41 t.Run("Voting with non-existing account fails", testVotingOnBatchWithNonExistingAccountFails) 42 t.Run("Voting without token fails", testVotingOnBatchWithoutTokenFails) 43 } 44 45 func testSubmittingBatchProposalDeclined(t *testing.T) { 46 eng := getTestEngine(t, time.Now()) 47 48 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 49 party := vgrand.RandomStr(5) 50 51 eng.ensureAllAssetEnabled(t) 52 eng.ensureTokenBalanceForParty(t, party, 1) 53 54 batchID := eng.newProposalID() 55 56 newFormProposal := eng.newFreeformProposal(party, now) 57 newNetParamProposal := eng.newProposalForNetParam(party, netparams.MarketAuctionMaximumDuration, "10h", now) 58 newMarketProposal := eng.newProposalForNewMarket(party, now, nil, nil, true) 59 60 // expect 61 eng.expectOpenProposalEvent(t, party, batchID) 62 eng.expectProposalEvents(t, []expectedProposal{ 63 { 64 partyID: party, 65 proposalID: newFormProposal.ID, 66 state: types.ProposalStateOpen, 67 reason: types.ProposalErrorUnspecified, 68 }, 69 { 70 partyID: party, 71 proposalID: newNetParamProposal.ID, 72 state: types.ProposalStateOpen, 73 reason: types.ProposalErrorUnspecified, 74 }, 75 { 76 partyID: party, 77 proposalID: newMarketProposal.ID, 78 state: types.ProposalStateOpen, 79 reason: types.ProposalErrorUnspecified, 80 }, 81 }) 82 83 batchClosingTime := now.Add(48 * time.Hour) 84 85 // when 86 _, err := eng.submitBatchProposal(t, eng.newBatchSubmission( 87 batchClosingTime.Unix(), 88 newFormProposal, 89 newNetParamProposal, 90 newMarketProposal, 91 ), batchID, party) 92 93 assert.NoError(t, err) 94 ctx := context.Background() 95 96 eng.expectDeclinedProposalEvent(t, batchID, types.ProposalErrorProposalInBatchDeclined) 97 eng.expectProposalEvents(t, []expectedProposal{ 98 { 99 partyID: party, 100 proposalID: newFormProposal.ID, 101 state: types.ProposalStateDeclined, 102 reason: types.ProposalErrorParticipationThresholdNotReached, 103 }, 104 { 105 partyID: party, 106 proposalID: newNetParamProposal.ID, 107 state: types.ProposalStateDeclined, 108 reason: types.ProposalErrorParticipationThresholdNotReached, 109 }, 110 { 111 partyID: party, 112 proposalID: newMarketProposal.ID, 113 state: types.ProposalStateDeclined, 114 reason: types.ProposalErrorParticipationThresholdNotReached, 115 }, 116 }) 117 118 eng.accounts.EXPECT().GetStakingAssetTotalSupply().AnyTimes().Return(num.NewUint(200)) 119 eng.OnTick(ctx, batchClosingTime.Add(1*time.Second)) 120 } 121 122 func testSubmittingBatchProposalPassed(t *testing.T) { 123 eng := getTestEngine(t, time.Now()) 124 125 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 126 party := vgrand.RandomStr(5) 127 128 eng.ensureAllAssetEnabled(t) 129 eng.ensureTokenBalanceForParty(t, party, 1) 130 131 batchID := eng.newProposalID() 132 133 newFormProposal := eng.newFreeformProposal(party, now) 134 newNetParamProposal := eng.newProposalForNetParam(party, netparams.MarketAuctionMaximumDuration, "10h", now) 135 newMarketProposal := eng.newProposalForNewMarket(party, now, nil, nil, true) 136 137 // expect 138 eng.expectOpenProposalEvent(t, party, batchID) 139 eng.expectProposalEvents(t, []expectedProposal{ 140 { 141 partyID: party, 142 proposalID: newFormProposal.ID, 143 state: types.ProposalStateOpen, 144 reason: types.ProposalErrorUnspecified, 145 }, 146 { 147 partyID: party, 148 proposalID: newNetParamProposal.ID, 149 state: types.ProposalStateOpen, 150 reason: types.ProposalErrorUnspecified, 151 }, 152 { 153 partyID: party, 154 proposalID: newMarketProposal.ID, 155 state: types.ProposalStateOpen, 156 reason: types.ProposalErrorUnspecified, 157 }, 158 }) 159 160 batchClosingTime := now.Add(48 * time.Hour) 161 162 // when 163 _, err := eng.submitBatchProposal(t, eng.newBatchSubmission( 164 batchClosingTime.Unix(), 165 newFormProposal, 166 newNetParamProposal, 167 newMarketProposal, 168 ), batchID, party) 169 170 assert.NoError(t, err) 171 ctx := context.Background() 172 173 eng.accounts.EXPECT().GetStakingAssetTotalSupply().AnyTimes().Return(num.NewUint(200)) 174 175 for i := 0; i < 10; i++ { 176 partyID := fmt.Sprintf("party-%d", i) 177 eng.ensureTokenBalanceForParty(t, partyID, 20) 178 eng.expectVoteEvent(t, partyID, batchID) 179 err = eng.addYesVote(t, partyID, batchID) 180 assert.NoError(t, err) 181 } 182 183 eng.expectPassedProposalEvent(t, batchID) 184 185 expectedProposals := []expectedProposal{ 186 { 187 partyID: party, 188 proposalID: newFormProposal.ID, 189 state: types.ProposalStatePassed, 190 }, 191 { 192 partyID: party, 193 proposalID: newNetParamProposal.ID, 194 state: types.ProposalStatePassed, 195 }, 196 { 197 partyID: party, 198 proposalID: newMarketProposal.ID, 199 state: types.ProposalStatePassed, 200 }, 201 } 202 203 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(evts []events.Event) { 204 i := 0 205 for _, evt := range evts { 206 switch e := evt.(type) { 207 case *events.Proposal: 208 p := e.Proposal() 209 assert.Equal(t, expectedProposals[i].proposalID, p.Id) 210 assert.Equal(t, expectedProposals[i].partyID, p.PartyId) 211 assert.Equal(t, expectedProposals[i].state.String(), p.State.String()) 212 i++ 213 } 214 } 215 }) 216 217 eng.OnTick(ctx, batchClosingTime.Add(1*time.Second)) 218 } 219 220 func testSubmittingBatchProposalFailsWhenTermValidationFails(t *testing.T) { 221 eng := getTestEngine(t, time.Now()) 222 223 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 224 party := vgrand.RandomStr(5) 225 226 newFormProposal := eng.newFreeformProposal(party, now) 227 newNetParamProposal := eng.newProposalForNetParam(party, netparams.MarketAuctionMaximumDuration, "10h", now) 228 newMarketProposal := eng.newProposalForNewMarket(party, now, nil, nil, true) 229 newMarketProposal.Terms.EnactmentTimestamp = time.Now().Unix() 230 231 newFormProposal2 := eng.newFreeformProposal(party, now) 232 newNetParamProposal2 := eng.newProposalForNetParam(party, netparams.MarketAuctionMaximumDuration, "10h", now) 233 newNetParamProposal2.Terms.EnactmentTimestamp = now.Add(24 * 365 * time.Hour).Unix() 234 newMarketProposal2 := eng.newProposalForNewMarket(party, now, nil, nil, true) 235 236 batchClosingTime := now.Add(48 * time.Hour).Unix() 237 238 cases := []struct { 239 msg string 240 submission types.BatchProposalSubmission 241 expectProposal []expectedProposal 242 containsError string 243 }{ 244 { 245 msg: "New market rejected and other proposals with it", 246 containsError: "proposal enactment time too soon", 247 submission: eng.newBatchSubmission( 248 batchClosingTime, 249 newFormProposal, 250 newNetParamProposal, 251 newMarketProposal, 252 ), 253 expectProposal: []expectedProposal{ 254 { 255 partyID: party, 256 proposalID: newFormProposal.ID, 257 state: types.ProposalStateRejected, 258 reason: types.ProposalErrorProposalInBatchRejected, 259 }, 260 { 261 partyID: party, 262 proposalID: newNetParamProposal.ID, 263 state: types.ProposalStateRejected, 264 reason: types.ProposalErrorProposalInBatchRejected, 265 }, 266 { 267 partyID: party, 268 proposalID: newMarketProposal.ID, 269 state: types.ProposalStateRejected, 270 reason: types.ProposalErrorEnactTimeTooSoon, 271 }, 272 }, 273 }, 274 { 275 msg: "Net parameter is rejected and the whole batch with it", 276 containsError: "proposal enactment time too late", 277 submission: eng.newBatchSubmission( 278 batchClosingTime, 279 newNetParamProposal2, 280 newFormProposal2, 281 newMarketProposal2, 282 ), 283 expectProposal: []expectedProposal{ 284 { 285 partyID: party, 286 proposalID: newNetParamProposal2.ID, 287 state: types.ProposalStateRejected, 288 reason: types.ProposalErrorEnactTimeTooLate, 289 }, 290 { 291 partyID: party, 292 proposalID: newFormProposal2.ID, 293 state: types.ProposalStateRejected, 294 reason: types.ProposalErrorProposalInBatchRejected, 295 }, 296 { 297 partyID: party, 298 proposalID: newMarketProposal2.ID, 299 state: types.ProposalStateRejected, 300 reason: types.ProposalErrorProposalInBatchRejected, 301 }, 302 }, 303 }, 304 } 305 306 for _, tc := range cases { 307 t.Run(tc.msg, func(tt *testing.T) { 308 // setup 309 eng.ensureAllAssetEnabled(tt) 310 eng.ensureTokenBalanceForParty(t, party, 1) 311 312 batchID := eng.newProposalID() 313 314 // expect 315 eng.expectRejectedProposalEvent(tt, party, batchID, types.ProposalErrorProposalInBatchRejected) 316 eng.expectProposalEvents(tt, tc.expectProposal) 317 318 // when 319 _, err := eng.submitBatchProposal(tt, tc.submission, batchID, party) 320 321 // then 322 require.Error(tt, err) 323 if tc.containsError != "" { 324 assert.Contains(tt, err.Error(), tc.containsError) 325 } 326 }) 327 } 328 } 329 330 func testVotingOnBatchWithNonExistingAccountFails(t *testing.T) { 331 eng := getTestEngine(t, time.Now()) 332 333 // given 334 proposer := vgrand.RandomStr(5) 335 proposal := eng.newProposalForNewMarket(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 336 337 // setup 338 eng.ensureAllAssetEnabled(t) 339 eng.ensureTokenBalanceForParty(t, proposer, 1) 340 341 batchID := eng.newProposalID() 342 343 // expect 344 eng.expectOpenProposalEvent(t, proposer, batchID) 345 eng.expectProposalEvents(t, []expectedProposal{ 346 { 347 partyID: proposer, 348 proposalID: proposal.ID, 349 state: types.ProposalStateOpen, 350 }, 351 }) 352 353 // when 354 sub := eng.newBatchSubmission( 355 proposal.Terms.ClosingTimestamp, 356 proposal, 357 ) 358 _, err := eng.submitBatchProposal(t, sub, batchID, proposer) 359 360 // then 361 require.NoError(t, err) 362 363 // given 364 voterWithoutAccount := "voter-no-account" 365 366 // setup 367 eng.ensureNoAccountForParty(t, voterWithoutAccount) 368 369 // when 370 err = eng.addYesVote(t, voterWithoutAccount, batchID) 371 372 // then 373 require.Error(t, err) 374 assert.ErrorContains(t, err, "no balance for party") 375 } 376 377 func testVotingOnBatchWithoutTokenFails(t *testing.T) { 378 eng := getTestEngine(t, time.Now()) 379 380 // given 381 proposer := eng.newValidParty("proposer", 1) 382 proposal := eng.newProposalForNewMarket(proposer.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour), nil, nil, true) 383 384 // setup 385 batchID := eng.newProposalID() 386 387 eng.ensureAllAssetEnabled(t) 388 eng.expectOpenProposalEvent(t, proposer.Id, batchID) 389 eng.expectProposalEvents(t, []expectedProposal{ 390 { 391 partyID: proposer.Id, 392 proposalID: proposal.ID, 393 state: types.ProposalStateOpen, 394 }, 395 }) 396 397 // when 398 sub := eng.newBatchSubmission( 399 proposal.Terms.ClosingTimestamp, 400 proposal, 401 ) 402 _, err := eng.submitBatchProposal(t, sub, batchID, proposer.Id) 403 404 // then 405 require.NoError(t, err) 406 407 // given 408 voterWithEmptyAccount := vgrand.RandomStr(5) 409 410 // setup 411 eng.ensureTokenBalanceForParty(t, voterWithEmptyAccount, 0) 412 413 // when 414 err = eng.addYesVote(t, voterWithEmptyAccount, batchID) 415 416 // then 417 require.Error(t, err) 418 assert.ErrorContains(t, err, governance.ErrVoterInsufficientTokens.Error()) 419 }