code.vegaprotocol.io/vega@v0.79.0/core/governance/engine_new_asset_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 "testing" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/assets" 24 "code.vegaprotocol.io/vega/core/assets/builtin" 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/governance" 27 "code.vegaprotocol.io/vega/core/netparams" 28 "code.vegaprotocol.io/vega/core/types" 29 "code.vegaprotocol.io/vega/core/validators" 30 "code.vegaprotocol.io/vega/libs/num" 31 vgrand "code.vegaprotocol.io/vega/libs/rand" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 ) 37 38 func TestProposalForNewAsset(t *testing.T) { 39 t.Run("Submitting a proposal for new asset succeeds", testSubmittingProposalForNewAssetSucceeds) 40 t.Run("Submitting a proposal for new asset with closing time before validation time fails", testSubmittingProposalForNewAssetWithClosingTimeBeforeValidationTimeFails) 41 t.Run("Voting during validation of proposal for new asset succeeds", testVotingDuringValidationOfProposalForNewAssetSucceeds) 42 t.Run("Rejects erc20 proposals for address already used", testRejectsERC20ProposalForAddressAlreadyUsed) 43 t.Run("Rejects erc20 proposals for unknown chain id", testRejectsERC20ProposalForUnknownChainID) 44 } 45 46 func testRejectsERC20ProposalForAddressAlreadyUsed(t *testing.T) { 47 eng := getTestEngine(t, time.Now()) 48 49 // given 50 party := eng.newValidParty("a-valid-party", 123456789) 51 proposal := eng.newProposalForNewAsset(party.Id, eng.tsvc.GetTimeNow().Add(48*time.Hour)) 52 53 newAssetERC20 := newAssetTerms() 54 newAssetERC20.NewAsset.Changes.Source = &types.AssetDetailsErc20{ 55 ERC20: &types.ERC20{ 56 ContractAddress: "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", 57 LifetimeLimit: num.NewUint(1), 58 WithdrawThreshold: num.NewUint(1), 59 ChainID: "1", 60 }, 61 } 62 proposal.Terms.Change = newAssetERC20 63 64 // setup 65 eng.assets.EXPECT().ValidateEthereumAddress("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", "1").Times(1).Return(assets.ErrErc20AddressAlreadyInUse) 66 67 // setup 68 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorERC20AddressAlreadyInUse) 69 70 // when 71 toSubmit, err := eng.submitProposal(t, proposal) 72 73 require.EqualError(t, err, assets.ErrErc20AddressAlreadyInUse.Error()) 74 require.Nil(t, toSubmit) 75 } 76 77 func testSubmittingProposalForNewAssetSucceeds(t *testing.T) { 78 eng := getTestEngine(t, time.Now()) 79 80 // given 81 party := eng.newValidParty("a-valid-party", 123456789) 82 proposal := eng.newProposalForNewAsset(party.Id, eng.tsvc.GetTimeNow().Add(2*time.Hour)) 83 84 // setup 85 eng.assets.EXPECT().NewAsset(gomock.Any(), proposal.ID, gomock.Any()).Times(1).Return(proposal.ID, nil) 86 eng.witness.EXPECT().StartCheck(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil) 87 88 // expect 89 eng.expectProposalWaitingForNodeVoteEvent(t, party.Id, proposal.ID) 90 91 // when 92 toSubmit, err := eng.submitProposal(t, proposal) 93 94 // then 95 require.NoError(t, err) 96 require.NotNil(t, toSubmit) 97 assert.False(t, toSubmit.IsNewMarket()) 98 require.Nil(t, toSubmit.NewMarket()) 99 } 100 101 func testSubmittingProposalForNewAssetWithClosingTimeBeforeValidationTimeFails(t *testing.T) { 102 eng := getTestEngine(t, time.Now()) 103 104 // given 105 party := vgrand.RandomStr(5) 106 proposal := eng.newProposalForNewAsset(party, eng.tsvc.GetTimeNow().Add(48*time.Hour)) 107 proposal.Terms.ValidationTimestamp = proposal.Terms.ClosingTimestamp + 10 108 109 // setup 110 eng.expectRejectedProposalEvent(t, party, proposal.ID, types.ProposalErrorIncompatibleTimestamps) 111 112 // when 113 _, err := eng.submitProposal(t, proposal) 114 115 // then 116 require.Error(t, err) 117 assert.Contains(t, err.Error(), "proposal closing time cannot be before validation time, expected >") 118 } 119 120 func testVotingDuringValidationOfProposalForNewAssetSucceeds(t *testing.T) { 121 eng := getTestEngine(t, time.Now()) 122 123 // when 124 proposer := vgrand.RandomStr(5) 125 proposal := eng.newProposalForNewAsset(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour)) 126 127 // setup 128 var bAsset *assets.Asset 129 var fcheck func(interface{}, bool) 130 var rescheck validators.Resource 131 eng.assets.EXPECT().NewAsset(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(func(_ context.Context, ref string, assetDetails *types.AssetDetails) (string, error) { 132 bAsset = assets.NewAsset(builtin.New(ref, assetDetails)) 133 return ref, nil 134 }) 135 eng.assets.EXPECT().Get(gomock.Any()).Times(1).DoAndReturn(func(id string) (*assets.Asset, error) { 136 return bAsset, nil 137 }) 138 eng.witness.EXPECT().StartCheck(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Do(func(r validators.Resource, f func(interface{}, bool), _ time.Time) error { 139 fcheck = f 140 rescheck = r 141 return nil 142 }) 143 eng.ensureStakingAssetTotalSupply(t, 9) 144 eng.ensureAllAssetEnabled(t) 145 eng.ensureTokenBalanceForParty(t, proposer, 1) 146 147 // expect 148 eng.expectProposalWaitingForNodeVoteEvent(t, proposer, proposal.ID) 149 150 // when 151 _, err := eng.submitProposal(t, proposal) 152 153 // then 154 require.NoError(t, err) 155 156 // given 157 voter1 := vgrand.RandomStr(5) 158 159 // setup 160 eng.ensureTokenBalanceForParty(t, voter1, 7) 161 162 // expect 163 eng.expectVoteEvent(t, voter1, proposal.ID) 164 165 // then 166 err = eng.addYesVote(t, voter1, proposal.ID) 167 168 // call success on the validation 169 fcheck(rescheck, true) 170 171 // then 172 require.NoError(t, err) 173 afterValidation := time.Unix(proposal.Terms.ValidationTimestamp, 0).Add(time.Second) 174 175 // setup 176 eng.ensureTokenBalanceForParty(t, voter1, 7) 177 178 // expect 179 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 180 eng.expectGetMarketState(t, proposal.ID) 181 182 // when 183 eng.OnTick(context.Background(), afterValidation) 184 185 // given 186 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 187 188 // expect 189 eng.expectPassedProposalEvent(t, proposal.ID) 190 eng.expectTotalGovernanceTokenFromVoteEvents(t, "1", "7") 191 eng.assets.EXPECT().SetPendingListing(gomock.Any(), proposal.ID).Times(1) 192 193 // when 194 eng.OnTick(context.Background(), afterClosing) 195 196 // given 197 voter2 := vgrand.RandomStr(5) 198 199 // when 200 err = eng.addNoVote(t, voter2, proposal.ID) 201 202 // then 203 require.Error(t, err) 204 assert.EqualError(t, err, governance.ErrProposalNotOpenForVotes.Error()) 205 206 // given 207 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 208 209 // when 210 // no calculations, no state change, simply removed from governance engine 211 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 212 213 // then 214 require.Len(t, toBeEnacted, 1) 215 assert.Equal(t, proposal.ID, toBeEnacted[0].Proposal().ID) 216 217 // when 218 err = eng.addNoVote(t, voter2, proposal.ID) 219 220 // then 221 require.Error(t, err) 222 assert.EqualError(t, err, governance.ErrProposalDoesNotExist.Error()) 223 } 224 225 func TestVotingDuringValidationOfProposalForNewAssetInBatchSucceeds(t *testing.T) { 226 eng := getTestEngine(t, time.Now()) 227 228 now := eng.tsvc.GetTimeNow().Add(2 * time.Hour) 229 230 // when 231 proposer := vgrand.RandomStr(5) 232 batchID := eng.newProposalID() 233 234 newAssetProposal := eng.newProposalForNewAsset(proposer, now) 235 236 // setup 237 var bAsset *assets.Asset 238 var fcheck func(interface{}, bool) 239 var rescheck validators.Resource 240 eng.assets.EXPECT().NewAsset(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(func(_ context.Context, ref string, assetDetails *types.AssetDetails) (string, error) { 241 bAsset = assets.NewAsset(builtin.New(ref, assetDetails)) 242 return ref, nil 243 }) 244 eng.assets.EXPECT().Get(gomock.Any()).Times(1).DoAndReturn(func(id string) (*assets.Asset, error) { 245 return bAsset, nil 246 }) 247 eng.witness.EXPECT().StartCheck(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Do(func(r validators.Resource, f func(interface{}, bool), _ time.Time) error { 248 fcheck = f 249 rescheck = r 250 return nil 251 }) 252 eng.ensureStakingAssetTotalSupply(t, 9) 253 eng.ensureAllAssetEnabled(t) 254 eng.ensureTokenBalanceForParty(t, proposer, 1) 255 256 // expect 257 eng.expectProposalWaitingForNodeVoteEvent(t, proposer, batchID) 258 259 // eng.expectOpenProposalEvent(t, proposer, batchID) 260 eng.expectProposalEvents(t, []expectedProposal{ 261 { 262 partyID: proposer, 263 proposalID: newAssetProposal.ID, 264 state: types.ProposalStateWaitingForNodeVote, 265 reason: types.ProposalErrorUnspecified, 266 }, 267 }) 268 269 batchClosingTime := now.Add(48 * time.Hour) 270 271 // when 272 _, err := eng.submitBatchProposal(t, eng.newBatchSubmission( 273 batchClosingTime.Unix(), 274 newAssetProposal, 275 ), batchID, proposer) 276 277 assert.NoError(t, err) 278 279 // given 280 voter1 := vgrand.RandomStr(5) 281 282 // setup 283 eng.ensureTokenBalanceForParty(t, voter1, 7) 284 285 // expect 286 eng.expectVoteEvent(t, voter1, batchID) 287 288 // then 289 err = eng.addYesVote(t, voter1, batchID) 290 require.NoError(t, err) 291 292 _ = fcheck 293 _ = rescheck 294 // call success on the validation 295 fcheck(rescheck, true) 296 297 // then 298 afterValidation := time.Unix(newAssetProposal.Terms.ValidationTimestamp, 0).Add(time.Second) 299 300 // setup 301 eng.ensureTokenBalanceForParty(t, voter1, 7) 302 303 // expect 304 eng.expectOpenProposalEvent(t, proposer, batchID) 305 // and the inner proposals 306 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1) 307 // eng.expectGetMarketState(t, ID) 308 309 // when 310 eng.OnTick(context.Background(), afterValidation) 311 312 // given 313 afterClosing := time.Unix(newAssetProposal.Terms.ClosingTimestamp, 0).Add(time.Second) 314 315 // // expect 316 eng.expectPassedProposalEvent(t, batchID) 317 // and the inner proposals 318 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1) 319 320 // // when 321 eng.OnTick(context.Background(), afterClosing) 322 323 eng.assets.EXPECT().SetPendingListing(gomock.Any(), newAssetProposal.ID).Times(1) 324 eng.OnTick(context.Background(), afterClosing.Add(1*time.Minute)) 325 326 // when 327 toBeEnacted, _ := eng.OnTick(context.Background(), afterClosing.Add(48*time.Hour)) 328 require.Len(t, toBeEnacted, 1) 329 } 330 331 func TestNoVotesAnd0RequiredFails(t *testing.T) { 332 eng := getTestEngine(t, time.Now()) 333 334 ctx := context.Background() 335 eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.GovernanceProposalAssetRequiredParticipation, "0")).Times(1) 336 assert.NoError(t, 337 eng.netp.Update(ctx, 338 "governance.proposal.asset.requiredParticipation", 339 "0", 340 ), 341 ) 342 343 // when 344 proposer := vgrand.RandomStr(5) 345 proposal := eng.newProposalForNewAsset(proposer, eng.tsvc.GetTimeNow().Add(2*time.Hour)) 346 347 // setup 348 var fcheck func(interface{}, bool) 349 var rescheck validators.Resource 350 eng.assets.EXPECT().NewAsset(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).DoAndReturn(func(_ context.Context, ref string, assetDetails *types.AssetDetails) (string, error) { 351 return ref, nil 352 }) 353 eng.witness.EXPECT().StartCheck(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Do(func(r validators.Resource, f func(interface{}, bool), _ time.Time) error { 354 fcheck = f 355 rescheck = r 356 return nil 357 }) 358 eng.ensureStakingAssetTotalSupply(t, 9) 359 eng.ensureAllAssetEnabled(t) 360 eng.ensureTokenBalanceForParty(t, proposer, 1) 361 362 // expect 363 eng.expectProposalWaitingForNodeVoteEvent(t, proposer, proposal.ID) 364 365 // when 366 _, err := eng.submitProposal(t, proposal) 367 368 // then 369 require.NoError(t, err) 370 371 // call success on the validation 372 fcheck(rescheck, true) 373 374 // then 375 require.NoError(t, err) 376 afterValidation := time.Unix(proposal.Terms.ValidationTimestamp, 0).Add(time.Second) 377 378 // setup 379 // eng.ensureTokenBalanceForParty(t, voter1, 7) 380 381 // expect 382 eng.expectOpenProposalEvent(t, proposer, proposal.ID) 383 eng.expectGetMarketState(t, proposal.ID) 384 385 // when 386 eng.OnTick(context.Background(), afterValidation) 387 388 // given 389 afterClosing := time.Unix(proposal.Terms.ClosingTimestamp, 0).Add(time.Second) 390 391 // expect 392 eng.expectDeclinedProposalEvent(t, proposal.ID, types.ProposalErrorParticipationThresholdNotReached) 393 // empty list of votes 394 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1) 395 396 eng.assets.EXPECT().SetRejected(gomock.Any(), proposal.ID).Times(1) 397 398 // when 399 eng.OnTick(context.Background(), afterClosing) 400 401 // given 402 afterEnactment := time.Unix(proposal.Terms.EnactmentTimestamp, 0).Add(time.Second) 403 404 // when 405 // no calculations, no state change, simply removed from governance engine 406 toBeEnacted, _ := eng.OnTick(context.Background(), afterEnactment) 407 408 // then 409 require.Len(t, toBeEnacted, 0) 410 } 411 412 func testRejectsERC20ProposalForUnknownChainID(t *testing.T) { 413 eng := getTestEngine(t, time.Now()) 414 415 // given 416 party := eng.newValidParty("a-valid-party", 123456789) 417 proposal := eng.newProposalForNewAsset(party.Id, eng.tsvc.GetTimeNow().Add(48*time.Hour)) 418 419 newAssetERC20 := newAssetTerms() 420 newAssetERC20.NewAsset.Changes.Source = &types.AssetDetailsErc20{ 421 ERC20: &types.ERC20{ 422 ContractAddress: "0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", 423 LifetimeLimit: num.NewUint(1), 424 WithdrawThreshold: num.NewUint(1), 425 ChainID: "666", 426 }, 427 } 428 proposal.Terms.Change = newAssetERC20 429 430 // setup 431 eng.assets.EXPECT().ValidateEthereumAddress("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", "666").Times(1).Return(assets.ErrUnknownChainID) 432 433 // setup 434 eng.expectRejectedProposalEvent(t, party.Id, proposal.ID, types.ProposalErrorInvalidAssetDetails) 435 436 // when 437 toSubmit, err := eng.submitProposal(t, proposal) 438 439 require.EqualError(t, err, assets.ErrUnknownChainID.Error()) 440 require.Nil(t, toSubmit) 441 }