github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/executor/staker_tx_verification_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package executor 5 6 import ( 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 "go.uber.org/mock/gomock" 12 13 "github.com/MetalBlockchain/metalgo/database" 14 "github.com/MetalBlockchain/metalgo/ids" 15 "github.com/MetalBlockchain/metalgo/snow" 16 "github.com/MetalBlockchain/metalgo/snow/snowtest" 17 "github.com/MetalBlockchain/metalgo/utils" 18 "github.com/MetalBlockchain/metalgo/utils/constants" 19 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 20 "github.com/MetalBlockchain/metalgo/vms/components/avax" 21 "github.com/MetalBlockchain/metalgo/vms/components/verify" 22 "github.com/MetalBlockchain/metalgo/vms/platformvm/config" 23 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 24 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 25 "github.com/MetalBlockchain/metalgo/vms/platformvm/utxo" 26 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 27 ) 28 29 func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { 30 ctx := snowtest.Context(t, snowtest.PChainID) 31 32 type test struct { 33 name string 34 backendF func(*gomock.Controller) *Backend 35 stateF func(*gomock.Controller) state.Chain 36 sTxF func() *txs.Tx 37 txF func() *txs.AddPermissionlessValidatorTx 38 expectedErr error 39 } 40 41 var ( 42 // in the following tests we set the fork time for forks we want active 43 // to activeForkTime, which is ensured to be before any other time related 44 // quantity (based on now) 45 activeForkTime = time.Unix(0, 0) 46 now = time.Now().Truncate(time.Second) // after activeForkTime 47 48 subnetID = ids.GenerateTestID() 49 customAssetID = ids.GenerateTestID() 50 unsignedTransformTx = &txs.TransformSubnetTx{ 51 AssetID: customAssetID, 52 MinValidatorStake: 1, 53 MaxValidatorStake: 2, 54 MinStakeDuration: 3, 55 MaxStakeDuration: 4, 56 MinDelegationFee: 5, 57 } 58 transformTx = txs.Tx{ 59 Unsigned: unsignedTransformTx, 60 Creds: []verify.Verifiable{}, 61 } 62 // This tx already passed syntactic verification. 63 startTime = now.Add(time.Second) 64 endTime = startTime.Add(time.Second * time.Duration(unsignedTransformTx.MinStakeDuration)) 65 verifiedTx = txs.AddPermissionlessValidatorTx{ 66 BaseTx: txs.BaseTx{ 67 SyntacticallyVerified: true, 68 BaseTx: avax.BaseTx{ 69 NetworkID: ctx.NetworkID, 70 BlockchainID: ctx.ChainID, 71 Outs: []*avax.TransferableOutput{}, 72 Ins: []*avax.TransferableInput{}, 73 }, 74 }, 75 Validator: txs.Validator{ 76 NodeID: ids.GenerateTestNodeID(), 77 // Note: [Start] is not set here as it will be ignored 78 // Post-Durango in favor of the current chain time 79 End: uint64(endTime.Unix()), 80 Wght: unsignedTransformTx.MinValidatorStake, 81 }, 82 Subnet: subnetID, 83 StakeOuts: []*avax.TransferableOutput{ 84 { 85 Asset: avax.Asset{ 86 ID: customAssetID, 87 }, 88 }, 89 }, 90 ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ 91 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 92 Threshold: 1, 93 }, 94 DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ 95 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 96 Threshold: 1, 97 }, 98 DelegationShares: 20_000, 99 } 100 verifiedSignedTx = txs.Tx{ 101 Unsigned: &verifiedTx, 102 Creds: []verify.Verifiable{}, 103 } 104 ) 105 verifiedSignedTx.SetBytes([]byte{1}, []byte{2}) 106 107 tests := []test{ 108 { 109 name: "fail syntactic verification", 110 backendF: func(*gomock.Controller) *Backend { 111 return &Backend{ 112 Ctx: ctx, 113 Config: defaultTestConfig(t, durango, activeForkTime), 114 } 115 }, 116 stateF: func(*gomock.Controller) state.Chain { 117 return nil 118 }, 119 sTxF: func() *txs.Tx { 120 return nil 121 }, 122 txF: func() *txs.AddPermissionlessValidatorTx { 123 return nil 124 }, 125 expectedErr: txs.ErrNilSignedTx, 126 }, 127 { 128 name: "not bootstrapped", 129 backendF: func(*gomock.Controller) *Backend { 130 return &Backend{ 131 Ctx: ctx, 132 Config: defaultTestConfig(t, durango, activeForkTime), 133 Bootstrapped: &utils.Atomic[bool]{}, 134 } 135 }, 136 stateF: func(ctrl *gomock.Controller) state.Chain { 137 mockState := state.NewMockChain(ctrl) 138 mockState.EXPECT().GetTimestamp().Return(now) // chain time is after Durango fork activation since now.After(activeForkTime) 139 return mockState 140 }, 141 sTxF: func() *txs.Tx { 142 return &verifiedSignedTx 143 }, 144 txF: func() *txs.AddPermissionlessValidatorTx { 145 return &txs.AddPermissionlessValidatorTx{} 146 }, 147 expectedErr: nil, 148 }, 149 { 150 name: "start time too early", 151 backendF: func(*gomock.Controller) *Backend { 152 bootstrapped := &utils.Atomic[bool]{} 153 bootstrapped.Set(true) 154 return &Backend{ 155 Ctx: ctx, 156 Config: defaultTestConfig(t, cortina, activeForkTime), 157 Bootstrapped: bootstrapped, 158 } 159 }, 160 stateF: func(ctrl *gomock.Controller) state.Chain { 161 state := state.NewMockChain(ctrl) 162 state.EXPECT().GetTimestamp().Return(verifiedTx.StartTime()) 163 return state 164 }, 165 sTxF: func() *txs.Tx { 166 return &verifiedSignedTx 167 }, 168 txF: func() *txs.AddPermissionlessValidatorTx { 169 return &verifiedTx 170 }, 171 expectedErr: ErrTimestampNotBeforeStartTime, 172 }, 173 { 174 name: "weight too low", 175 backendF: func(*gomock.Controller) *Backend { 176 bootstrapped := &utils.Atomic[bool]{} 177 bootstrapped.Set(true) 178 return &Backend{ 179 Ctx: ctx, 180 Config: defaultTestConfig(t, durango, activeForkTime), 181 Bootstrapped: bootstrapped, 182 } 183 }, 184 stateF: func(ctrl *gomock.Controller) state.Chain { 185 state := state.NewMockChain(ctrl) 186 state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 187 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 188 return state 189 }, 190 sTxF: func() *txs.Tx { 191 return &verifiedSignedTx 192 }, 193 txF: func() *txs.AddPermissionlessValidatorTx { 194 tx := verifiedTx // Note that this copies [verifiedTx] 195 tx.Validator.Wght = unsignedTransformTx.MinValidatorStake - 1 196 return &tx 197 }, 198 expectedErr: ErrWeightTooSmall, 199 }, 200 { 201 name: "weight too high", 202 backendF: func(*gomock.Controller) *Backend { 203 bootstrapped := &utils.Atomic[bool]{} 204 bootstrapped.Set(true) 205 return &Backend{ 206 Ctx: ctx, 207 Config: defaultTestConfig(t, durango, activeForkTime), 208 Bootstrapped: bootstrapped, 209 } 210 }, 211 stateF: func(ctrl *gomock.Controller) state.Chain { 212 state := state.NewMockChain(ctrl) 213 state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 214 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 215 return state 216 }, 217 sTxF: func() *txs.Tx { 218 return &verifiedSignedTx 219 }, 220 txF: func() *txs.AddPermissionlessValidatorTx { 221 tx := verifiedTx // Note that this copies [verifiedTx] 222 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake + 1 223 return &tx 224 }, 225 expectedErr: ErrWeightTooLarge, 226 }, 227 { 228 name: "insufficient delegation fee", 229 backendF: func(*gomock.Controller) *Backend { 230 bootstrapped := &utils.Atomic[bool]{} 231 bootstrapped.Set(true) 232 return &Backend{ 233 Ctx: ctx, 234 Config: defaultTestConfig(t, durango, activeForkTime), 235 Bootstrapped: bootstrapped, 236 } 237 }, 238 stateF: func(ctrl *gomock.Controller) state.Chain { 239 state := state.NewMockChain(ctrl) 240 state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 241 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 242 return state 243 }, 244 sTxF: func() *txs.Tx { 245 return &verifiedSignedTx 246 }, 247 txF: func() *txs.AddPermissionlessValidatorTx { 248 tx := verifiedTx // Note that this copies [verifiedTx] 249 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 250 tx.DelegationShares = unsignedTransformTx.MinDelegationFee - 1 251 return &tx 252 }, 253 expectedErr: ErrInsufficientDelegationFee, 254 }, 255 { 256 name: "duration too short", 257 backendF: func(*gomock.Controller) *Backend { 258 bootstrapped := &utils.Atomic[bool]{} 259 bootstrapped.Set(true) 260 return &Backend{ 261 Ctx: ctx, 262 Config: defaultTestConfig(t, durango, activeForkTime), 263 Bootstrapped: bootstrapped, 264 } 265 }, 266 stateF: func(ctrl *gomock.Controller) state.Chain { 267 state := state.NewMockChain(ctrl) 268 state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 269 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 270 return state 271 }, 272 sTxF: func() *txs.Tx { 273 return &verifiedSignedTx 274 }, 275 txF: func() *txs.AddPermissionlessValidatorTx { 276 tx := verifiedTx // Note that this copies [verifiedTx] 277 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 278 tx.DelegationShares = unsignedTransformTx.MinDelegationFee 279 280 // Note the duration is 1 less than the minimum 281 tx.Validator.End = tx.Validator.Start + uint64(unsignedTransformTx.MinStakeDuration) - 1 282 return &tx 283 }, 284 expectedErr: ErrStakeTooShort, 285 }, 286 { 287 name: "duration too long", 288 backendF: func(*gomock.Controller) *Backend { 289 bootstrapped := &utils.Atomic[bool]{} 290 bootstrapped.Set(true) 291 return &Backend{ 292 Ctx: ctx, 293 Config: defaultTestConfig(t, durango, activeForkTime), 294 Bootstrapped: bootstrapped, 295 } 296 }, 297 stateF: func(ctrl *gomock.Controller) state.Chain { 298 state := state.NewMockChain(ctrl) 299 state.EXPECT().GetTimestamp().Return(time.Unix(1, 0)) // chain time is after fork activation since time.Unix(1, 0).After(activeForkTime) 300 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 301 return state 302 }, 303 sTxF: func() *txs.Tx { 304 return &verifiedSignedTx 305 }, 306 txF: func() *txs.AddPermissionlessValidatorTx { 307 tx := verifiedTx // Note that this copies [verifiedTx] 308 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 309 tx.DelegationShares = unsignedTransformTx.MinDelegationFee 310 311 // Note the duration is more than the maximum 312 tx.Validator.End = uint64(unsignedTransformTx.MaxStakeDuration) + 2 313 return &tx 314 }, 315 expectedErr: ErrStakeTooLong, 316 }, 317 { 318 name: "wrong assetID", 319 backendF: func(*gomock.Controller) *Backend { 320 bootstrapped := &utils.Atomic[bool]{} 321 bootstrapped.Set(true) 322 return &Backend{ 323 Ctx: ctx, 324 Config: defaultTestConfig(t, durango, activeForkTime), 325 Bootstrapped: bootstrapped, 326 } 327 }, 328 stateF: func(ctrl *gomock.Controller) state.Chain { 329 mockState := state.NewMockChain(ctrl) 330 mockState.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 331 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 332 return mockState 333 }, 334 sTxF: func() *txs.Tx { 335 return &verifiedSignedTx 336 }, 337 txF: func() *txs.AddPermissionlessValidatorTx { 338 tx := verifiedTx // Note that this copies [verifiedTx] 339 tx.StakeOuts = []*avax.TransferableOutput{ 340 { 341 Asset: avax.Asset{ 342 ID: ids.GenerateTestID(), 343 }, 344 }, 345 } 346 return &tx 347 }, 348 expectedErr: ErrWrongStakedAssetID, 349 }, 350 { 351 name: "duplicate validator", 352 backendF: func(*gomock.Controller) *Backend { 353 bootstrapped := &utils.Atomic[bool]{} 354 bootstrapped.Set(true) 355 return &Backend{ 356 Ctx: ctx, 357 Config: defaultTestConfig(t, durango, activeForkTime), 358 Bootstrapped: bootstrapped, 359 } 360 }, 361 stateF: func(ctrl *gomock.Controller) state.Chain { 362 mockState := state.NewMockChain(ctrl) 363 mockState.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) 364 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 365 // State says validator exists 366 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, nil) 367 return mockState 368 }, 369 sTxF: func() *txs.Tx { 370 return &verifiedSignedTx 371 }, 372 txF: func() *txs.AddPermissionlessValidatorTx { 373 return &verifiedTx 374 }, 375 expectedErr: ErrDuplicateValidator, 376 }, 377 { 378 name: "validator not subset of primary network validator", 379 backendF: func(*gomock.Controller) *Backend { 380 bootstrapped := &utils.Atomic[bool]{} 381 bootstrapped.Set(true) 382 return &Backend{ 383 Ctx: ctx, 384 Config: defaultTestConfig(t, durango, activeForkTime), 385 Bootstrapped: bootstrapped, 386 } 387 }, 388 stateF: func(ctrl *gomock.Controller) state.Chain { 389 mockState := state.NewMockChain(ctrl) 390 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 391 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 392 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 393 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 394 // Validator time isn't subset of primary network validator time 395 primaryNetworkVdr := &state.Staker{ 396 EndTime: verifiedTx.EndTime().Add(-1 * time.Second), 397 } 398 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 399 return mockState 400 }, 401 sTxF: func() *txs.Tx { 402 return &verifiedSignedTx 403 }, 404 txF: func() *txs.AddPermissionlessValidatorTx { 405 return &verifiedTx 406 }, 407 expectedErr: ErrPeriodMismatch, 408 }, 409 { 410 name: "flow check fails", 411 backendF: func(ctrl *gomock.Controller) *Backend { 412 bootstrapped := &utils.Atomic[bool]{} 413 bootstrapped.Set(true) 414 415 flowChecker := utxo.NewMockVerifier(ctrl) 416 flowChecker.EXPECT().VerifySpend( 417 gomock.Any(), 418 gomock.Any(), 419 gomock.Any(), 420 gomock.Any(), 421 gomock.Any(), 422 gomock.Any(), 423 ).Return(ErrFlowCheckFailed) 424 425 cfg := defaultTestConfig(t, durango, activeForkTime) 426 cfg.StaticFeeConfig.AddSubnetValidatorFee = 1 427 428 return &Backend{ 429 FlowChecker: flowChecker, 430 Config: cfg, 431 Ctx: ctx, 432 Bootstrapped: bootstrapped, 433 } 434 }, 435 stateF: func(ctrl *gomock.Controller) state.Chain { 436 mockState := state.NewMockChain(ctrl) 437 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 438 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 439 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 440 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 441 primaryNetworkVdr := &state.Staker{ 442 EndTime: mockable.MaxTime, 443 } 444 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 445 return mockState 446 }, 447 sTxF: func() *txs.Tx { 448 return &verifiedSignedTx 449 }, 450 txF: func() *txs.AddPermissionlessValidatorTx { 451 return &verifiedTx 452 }, 453 expectedErr: ErrFlowCheckFailed, 454 }, 455 { 456 name: "success", 457 backendF: func(ctrl *gomock.Controller) *Backend { 458 bootstrapped := &utils.Atomic[bool]{} 459 bootstrapped.Set(true) 460 461 flowChecker := utxo.NewMockVerifier(ctrl) 462 flowChecker.EXPECT().VerifySpend( 463 gomock.Any(), 464 gomock.Any(), 465 gomock.Any(), 466 gomock.Any(), 467 gomock.Any(), 468 gomock.Any(), 469 ).Return(nil) 470 471 cfg := defaultTestConfig(t, durango, activeForkTime) 472 cfg.StaticFeeConfig.AddSubnetValidatorFee = 1 473 474 return &Backend{ 475 FlowChecker: flowChecker, 476 Config: cfg, 477 Ctx: ctx, 478 Bootstrapped: bootstrapped, 479 } 480 }, 481 stateF: func(ctrl *gomock.Controller) state.Chain { 482 mockState := state.NewMockChain(ctrl) 483 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after Durango fork activation since now.After(activeForkTime) 484 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 485 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 486 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 487 primaryNetworkVdr := &state.Staker{ 488 EndTime: mockable.MaxTime, 489 } 490 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 491 return mockState 492 }, 493 sTxF: func() *txs.Tx { 494 return &verifiedSignedTx 495 }, 496 txF: func() *txs.AddPermissionlessValidatorTx { 497 return &verifiedTx 498 }, 499 expectedErr: nil, 500 }, 501 } 502 503 for _, tt := range tests { 504 t.Run(tt.name, func(t *testing.T) { 505 ctrl := gomock.NewController(t) 506 507 var ( 508 backend = tt.backendF(ctrl) 509 state = tt.stateF(ctrl) 510 sTx = tt.sTxF() 511 tx = tt.txF() 512 ) 513 514 err := verifyAddPermissionlessValidatorTx(backend, state, sTx, tx) 515 require.ErrorIs(t, err, tt.expectedErr) 516 }) 517 } 518 } 519 520 func TestGetValidatorRules(t *testing.T) { 521 type test struct { 522 name string 523 subnetID ids.ID 524 backend *Backend 525 chainStateF func(*gomock.Controller) state.Chain 526 expectedRules *addValidatorRules 527 expectedErr error 528 } 529 530 var ( 531 config = &config.Config{ 532 MinValidatorStake: 1, 533 MaxValidatorStake: 2, 534 MinStakeDuration: time.Second, 535 MaxStakeDuration: 2 * time.Second, 536 MinDelegationFee: 1337, 537 } 538 avaxAssetID = ids.GenerateTestID() 539 customAssetID = ids.GenerateTestID() 540 subnetID = ids.GenerateTestID() 541 ) 542 543 tests := []test{ 544 { 545 name: "primary network", 546 subnetID: constants.PrimaryNetworkID, 547 backend: &Backend{ 548 Config: config, 549 Ctx: &snow.Context{ 550 AVAXAssetID: avaxAssetID, 551 }, 552 }, 553 chainStateF: func(*gomock.Controller) state.Chain { 554 return nil 555 }, 556 expectedRules: &addValidatorRules{ 557 assetID: avaxAssetID, 558 minValidatorStake: config.MinValidatorStake, 559 maxValidatorStake: config.MaxValidatorStake, 560 minStakeDuration: config.MinStakeDuration, 561 maxStakeDuration: config.MaxStakeDuration, 562 minDelegationFee: config.MinDelegationFee, 563 }, 564 }, 565 { 566 name: "can't get subnet transformation", 567 subnetID: subnetID, 568 backend: nil, 569 chainStateF: func(ctrl *gomock.Controller) state.Chain { 570 state := state.NewMockChain(ctrl) 571 state.EXPECT().GetSubnetTransformation(subnetID).Return(nil, errTest) 572 return state 573 }, 574 expectedRules: &addValidatorRules{}, 575 expectedErr: errTest, 576 }, 577 { 578 name: "invalid transformation tx", 579 subnetID: subnetID, 580 backend: nil, 581 chainStateF: func(ctrl *gomock.Controller) state.Chain { 582 state := state.NewMockChain(ctrl) 583 tx := &txs.Tx{ 584 Unsigned: &txs.AddDelegatorTx{}, 585 } 586 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 587 return state 588 }, 589 expectedRules: &addValidatorRules{}, 590 expectedErr: ErrIsNotTransformSubnetTx, 591 }, 592 { 593 name: "subnet", 594 subnetID: subnetID, 595 backend: nil, 596 chainStateF: func(ctrl *gomock.Controller) state.Chain { 597 state := state.NewMockChain(ctrl) 598 tx := &txs.Tx{ 599 Unsigned: &txs.TransformSubnetTx{ 600 AssetID: customAssetID, 601 MinValidatorStake: config.MinValidatorStake, 602 MaxValidatorStake: config.MaxValidatorStake, 603 MinStakeDuration: 1337, 604 MaxStakeDuration: 42, 605 MinDelegationFee: config.MinDelegationFee, 606 }, 607 } 608 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 609 return state 610 }, 611 expectedRules: &addValidatorRules{ 612 assetID: customAssetID, 613 minValidatorStake: config.MinValidatorStake, 614 maxValidatorStake: config.MaxValidatorStake, 615 minStakeDuration: 1337 * time.Second, 616 maxStakeDuration: 42 * time.Second, 617 minDelegationFee: config.MinDelegationFee, 618 }, 619 expectedErr: nil, 620 }, 621 } 622 623 for _, tt := range tests { 624 t.Run(tt.name, func(t *testing.T) { 625 require := require.New(t) 626 ctrl := gomock.NewController(t) 627 628 chainState := tt.chainStateF(ctrl) 629 rules, err := getValidatorRules(tt.backend, chainState, tt.subnetID) 630 if tt.expectedErr != nil { 631 require.ErrorIs(err, tt.expectedErr) 632 return 633 } 634 require.NoError(err) 635 require.Equal(tt.expectedRules, rules) 636 }) 637 } 638 } 639 640 func TestGetDelegatorRules(t *testing.T) { 641 type test struct { 642 name string 643 subnetID ids.ID 644 backend *Backend 645 chainStateF func(*gomock.Controller) state.Chain 646 expectedRules *addDelegatorRules 647 expectedErr error 648 } 649 var ( 650 config = &config.Config{ 651 MinDelegatorStake: 1, 652 MaxValidatorStake: 2, 653 MinStakeDuration: time.Second, 654 MaxStakeDuration: 2 * time.Second, 655 } 656 avaxAssetID = ids.GenerateTestID() 657 customAssetID = ids.GenerateTestID() 658 subnetID = ids.GenerateTestID() 659 ) 660 tests := []test{ 661 { 662 name: "primary network", 663 subnetID: constants.PrimaryNetworkID, 664 backend: &Backend{ 665 Config: config, 666 Ctx: &snow.Context{ 667 AVAXAssetID: avaxAssetID, 668 }, 669 }, 670 chainStateF: func(*gomock.Controller) state.Chain { 671 return nil 672 }, 673 expectedRules: &addDelegatorRules{ 674 assetID: avaxAssetID, 675 minDelegatorStake: config.MinDelegatorStake, 676 maxValidatorStake: config.MaxValidatorStake, 677 minStakeDuration: config.MinStakeDuration, 678 maxStakeDuration: config.MaxStakeDuration, 679 maxValidatorWeightFactor: MaxValidatorWeightFactor, 680 }, 681 }, 682 { 683 name: "can't get subnet transformation", 684 subnetID: subnetID, 685 backend: nil, 686 chainStateF: func(ctrl *gomock.Controller) state.Chain { 687 state := state.NewMockChain(ctrl) 688 state.EXPECT().GetSubnetTransformation(subnetID).Return(nil, errTest) 689 return state 690 }, 691 expectedRules: &addDelegatorRules{}, 692 expectedErr: errTest, 693 }, 694 { 695 name: "invalid transformation tx", 696 subnetID: subnetID, 697 backend: nil, 698 chainStateF: func(ctrl *gomock.Controller) state.Chain { 699 state := state.NewMockChain(ctrl) 700 tx := &txs.Tx{ 701 Unsigned: &txs.AddDelegatorTx{}, 702 } 703 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 704 return state 705 }, 706 expectedRules: &addDelegatorRules{}, 707 expectedErr: ErrIsNotTransformSubnetTx, 708 }, 709 { 710 name: "subnet", 711 subnetID: subnetID, 712 backend: nil, 713 chainStateF: func(ctrl *gomock.Controller) state.Chain { 714 state := state.NewMockChain(ctrl) 715 tx := &txs.Tx{ 716 Unsigned: &txs.TransformSubnetTx{ 717 AssetID: customAssetID, 718 MinDelegatorStake: config.MinDelegatorStake, 719 MinValidatorStake: config.MinValidatorStake, 720 MaxValidatorStake: config.MaxValidatorStake, 721 MinStakeDuration: 1337, 722 MaxStakeDuration: 42, 723 MinDelegationFee: config.MinDelegationFee, 724 MaxValidatorWeightFactor: 21, 725 }, 726 } 727 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 728 return state 729 }, 730 expectedRules: &addDelegatorRules{ 731 assetID: customAssetID, 732 minDelegatorStake: config.MinDelegatorStake, 733 maxValidatorStake: config.MaxValidatorStake, 734 minStakeDuration: 1337 * time.Second, 735 maxStakeDuration: 42 * time.Second, 736 maxValidatorWeightFactor: 21, 737 }, 738 expectedErr: nil, 739 }, 740 } 741 for _, tt := range tests { 742 t.Run(tt.name, func(t *testing.T) { 743 require := require.New(t) 744 ctrl := gomock.NewController(t) 745 746 chainState := tt.chainStateF(ctrl) 747 rules, err := getDelegatorRules(tt.backend, chainState, tt.subnetID) 748 if tt.expectedErr != nil { 749 require.ErrorIs(err, tt.expectedErr) 750 return 751 } 752 require.NoError(err) 753 require.Equal(tt.expectedRules, rules) 754 }) 755 } 756 }