github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/handlers_test.go (about) 1 // Copyright (c) 2022 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package staking 7 8 import ( 9 "context" 10 "encoding/hex" 11 "encoding/json" 12 "math" 13 "math/big" 14 "testing" 15 "time" 16 17 "github.com/agiledragon/gomonkey/v2" 18 "github.com/golang/mock/gomock" 19 "github.com/mohae/deepcopy" 20 "github.com/pkg/errors" 21 "github.com/stretchr/testify/require" 22 23 "github.com/iotexproject/go-pkgs/crypto" 24 "github.com/iotexproject/iotex-address/address" 25 "github.com/iotexproject/iotex-proto/golang/iotextypes" 26 27 "github.com/iotexproject/iotex-core/action" 28 "github.com/iotexproject/iotex-core/action/protocol" 29 accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" 30 "github.com/iotexproject/iotex-core/blockchain/genesis" 31 "github.com/iotexproject/iotex-core/pkg/unit" 32 "github.com/iotexproject/iotex-core/state" 33 "github.com/iotexproject/iotex-core/test/identityset" 34 "github.com/iotexproject/iotex-core/testutil/testdb" 35 ) 36 37 const ( 38 _reclaim = "This is to certify I am transferring the ownership of said bucket to said recipient on IoTeX blockchain" 39 ) 40 41 // Delete should only be used by test 42 func (m *CandidateCenter) deleteForTestOnly(owner address.Address) { 43 if owner == nil { 44 return 45 } 46 47 if _, hit := m.base.getByOwner(owner.String()); hit { 48 m.base.delete(owner) 49 if !m.change.containsOwner(owner) { 50 m.size-- 51 return 52 } 53 } 54 55 if m.change.containsOwner(owner) { 56 if owner != nil { 57 candidates := []*Candidate{} 58 for _, c := range m.change.candidates { 59 if c.Owner.String() != owner.String() { 60 candidates = append(candidates, c) 61 } 62 } 63 m.change.candidates = candidates 64 delete(m.change.dirty, owner.String()) 65 return 66 } 67 m.size-- 68 } 69 } 70 71 func TestProtocol_HandleCreateStake(t *testing.T) { 72 require := require.New(t) 73 74 ctrl := gomock.NewController(t) 75 sm := testdb.NewMockStateManager(ctrl) 76 csm := newCandidateStateManager(sm) 77 csr := newCandidateStateReader(sm) 78 _, err := sm.PutState( 79 &totalBucketCount{count: 0}, 80 protocol.NamespaceOption(_stakingNameSpace), 81 protocol.KeyOption(TotalBucketKey), 82 ) 83 require.NoError(err) 84 85 // create protocol 86 p, err := NewProtocol(depositGas, &BuilderConfig{ 87 Staking: genesis.Default.Staking, 88 PersistStakingPatchBlock: math.MaxUint64, 89 }, nil, nil, genesis.Default.GreenlandBlockHeight) 90 require.NoError(err) 91 92 // set up candidate 93 candidate := testCandidates[0].d.Clone() 94 require.NoError(csm.putCandidate(candidate)) 95 candidateName := candidate.Name 96 candidateAddr := candidate.Owner 97 ctx := genesis.WithGenesisContext(context.Background(), genesis.Default) 98 ctx = protocol.WithFeatureWithHeightCtx(ctx) 99 v, err := p.Start(ctx, sm) 100 require.NoError(err) 101 cc, ok := v.(*ViewData) 102 require.True(ok) 103 require.NoError(sm.WriteView(_protocolID, cc)) 104 105 stakerAddr := identityset.Address(1) 106 tests := []struct { 107 // action fields 108 initBalance int64 109 candName string 110 amount string 111 duration uint32 112 autoStake bool 113 gasPrice *big.Int 114 gasLimit uint64 115 nonce uint64 116 // block context 117 blkHeight uint64 118 blkTimestamp time.Time 119 blkGasLimit uint64 120 // expected result 121 err error 122 status iotextypes.ReceiptStatus 123 }{ 124 { 125 10, 126 candidateName, 127 "100000000000000000000", 128 1, 129 false, 130 big.NewInt(unit.Qev), 131 10000, 132 1, 133 1, 134 time.Now(), 135 10000, 136 nil, 137 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 138 }, 139 { 140 100, 141 "notExist", 142 "100000000000000000000", 143 1, 144 false, 145 big.NewInt(unit.Qev), 146 10000, 147 2, 148 1, 149 time.Now(), 150 10000, 151 action.ErrInvalidCanName, 152 iotextypes.ReceiptStatus_ErrCandidateNotExist, 153 }, 154 { 155 101, 156 candidateName, 157 "10000000000000000000", 158 1, 159 false, 160 big.NewInt(unit.Qev), 161 10000, 162 2, 163 1, 164 time.Now(), 165 10000, 166 action.ErrInvalidAmount, 167 iotextypes.ReceiptStatus_Failure, 168 }, 169 { 170 101, 171 candidateName, 172 "100000000000000000000", 173 1, 174 false, 175 big.NewInt(unit.Qev), 176 10000, 177 2, 178 1, 179 time.Now(), 180 10000, 181 nil, 182 iotextypes.ReceiptStatus_Success, 183 }, 184 } 185 186 for _, test := range tests { 187 require.NoError(setupAccount(sm, stakerAddr, test.initBalance)) 188 ctx := protocol.WithActionCtx(ctx, protocol.ActionCtx{ 189 Caller: stakerAddr, 190 GasPrice: test.gasPrice, 191 IntrinsicGas: test.gasLimit, 192 Nonce: test.nonce, 193 }) 194 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 195 BlockHeight: test.blkHeight, 196 BlockTimeStamp: test.blkTimestamp, 197 GasLimit: test.blkGasLimit, 198 }) 199 act, err := action.NewCreateStake(test.nonce, test.candName, test.amount, test.duration, test.autoStake, 200 nil, test.gasLimit, test.gasPrice) 201 require.NoError(err) 202 err = p.Validate(ctx, act, sm) 203 if test.err != nil { 204 require.EqualError(test.err, errors.Cause(err).Error()) 205 continue 206 } 207 ctx = protocol.WithFeatureCtx(ctx) 208 r, err := p.Handle(ctx, act, sm) 209 require.NoError(err) 210 require.Equal(uint64(test.status), r.Status) 211 212 if test.status == iotextypes.ReceiptStatus_Success { 213 // check the special create bucket log 214 tLogs := r.TransactionLogs() 215 require.Equal(1, len(tLogs)) 216 cLog := tLogs[0] 217 require.Equal(stakerAddr.String(), cLog.Sender) 218 require.Equal(address.StakingBucketPoolAddr, cLog.Recipient) 219 require.Equal(test.amount, cLog.Amount.String()) 220 221 // test bucket index and bucket 222 bucketIndices, _, err := csr.candBucketIndices(candidateAddr) 223 require.NoError(err) 224 require.Equal(1, len(*bucketIndices)) 225 bucketIndices, _, err = csr.voterBucketIndices(stakerAddr) 226 require.NoError(err) 227 require.Equal(1, len(*bucketIndices)) 228 indices := *bucketIndices 229 bucket, err := csr.getBucket(indices[0]) 230 require.NoError(err) 231 require.Equal(candidateAddr, bucket.Candidate) 232 require.Equal(stakerAddr, bucket.Owner) 233 require.Equal(test.amount, bucket.StakedAmount.String()) 234 235 // test candidate 236 candidate, _, err := csr.getCandidate(candidateAddr) 237 require.NoError(err) 238 require.LessOrEqual(test.amount, candidate.Votes.String()) 239 csm, err := NewCandidateStateManager(sm, false) 240 require.NoError(err) 241 candidate = csm.GetByOwner(candidateAddr) 242 require.NotNil(candidate) 243 require.LessOrEqual(test.amount, candidate.Votes.String()) 244 245 // test staker's account 246 caller, err := accountutil.LoadAccount(sm, stakerAddr) 247 require.NoError(err) 248 actCost, err := act.Cost() 249 require.NoError(err) 250 require.Equal(unit.ConvertIotxToRau(test.initBalance), big.NewInt(0).Add(caller.Balance, actCost)) 251 require.Equal(test.nonce+1, caller.PendingNonce()) 252 } 253 } 254 } 255 256 func TestProtocol_HandleCandidateRegister(t *testing.T) { 257 require := require.New(t) 258 ctrl := gomock.NewController(t) 259 sm, p, _, _ := initAll(t, ctrl) 260 csr := newCandidateStateReader(sm) 261 tests := []struct { 262 initBalance int64 263 caller address.Address 264 nonce uint64 265 name string 266 operatorAddrStr string 267 rewardAddrStr string 268 ownerAddrStr string 269 amountStr string 270 votesStr string 271 duration uint32 272 autoStake bool 273 payload []byte 274 gasLimit uint64 275 blkGasLimit uint64 276 gasPrice *big.Int 277 newProtocol bool 278 err error 279 status iotextypes.ReceiptStatus 280 }{ 281 // fetchCaller,ErrNotEnoughBalance 282 { 283 1200000, 284 identityset.Address(27), 285 1, 286 "test", 287 identityset.Address(28).String(), 288 identityset.Address(29).String(), 289 identityset.Address(30).String(), 290 "1200000000000000000000000", 291 "", 292 uint32(10000), 293 false, 294 []byte("payload"), 295 uint64(1000000), 296 uint64(1000000), 297 big.NewInt(1000), 298 true, 299 nil, 300 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 301 }, 302 // owner address is nil 303 { 304 1201000, 305 identityset.Address(27), 306 1, 307 "test", 308 identityset.Address(28).String(), 309 identityset.Address(29).String(), 310 "", 311 "1200000000000000000000000", 312 "1806204150552640363969204", 313 uint32(10000), 314 false, 315 nil, 316 uint64(1000000), 317 uint64(1000000), 318 big.NewInt(1), 319 true, 320 nil, 321 iotextypes.ReceiptStatus_Success, 322 }, 323 // Upsert,check collision 324 { 325 1201000, 326 identityset.Address(27), 327 2, 328 "test", 329 identityset.Address(28).String(), 330 identityset.Address(29).String(), 331 identityset.Address(30).String(), 332 "1200000000000000000000000", 333 "", 334 uint32(10000), 335 false, 336 []byte("payload"), 337 uint64(1000000), 338 uint64(1000000), 339 big.NewInt(1000), 340 false, 341 nil, 342 iotextypes.ReceiptStatus_ErrCandidateConflict, 343 }, 344 // invalid amount 345 { 346 1201000, 347 identityset.Address(27), 348 1, 349 "test", 350 identityset.Address(28).String(), 351 identityset.Address(29).String(), 352 identityset.Address(30).String(), 353 "120000000000000000000000", 354 "1806204150552640363969204", 355 uint32(10000), 356 false, 357 nil, 358 uint64(1000000), 359 uint64(1000000), 360 big.NewInt(1), 361 true, 362 action.ErrInvalidAmount, 363 iotextypes.ReceiptStatus_Failure, 364 }, 365 // invalid candidate name 366 { 367 1201000, 368 identityset.Address(27), 369 1, 370 "!invalid", 371 identityset.Address(28).String(), 372 identityset.Address(29).String(), 373 identityset.Address(30).String(), 374 "1200000000000000000000000", 375 "1806204150552640363969204", 376 uint32(10000), 377 false, 378 nil, 379 uint64(1000000), 380 uint64(1000000), 381 big.NewInt(1), 382 true, 383 action.ErrInvalidCanName, 384 iotextypes.ReceiptStatus_Failure, 385 }, 386 // success for the following test 387 { 388 1201000, 389 identityset.Address(27), 390 1, 391 "test", 392 identityset.Address(28).String(), 393 identityset.Address(29).String(), 394 identityset.Address(30).String(), 395 "1200000000000000000000000", 396 "1806204150552640363969204", 397 uint32(10000), 398 false, 399 nil, 400 uint64(1000000), 401 uint64(1000000), 402 big.NewInt(1), 403 true, 404 nil, 405 iotextypes.ReceiptStatus_Success, 406 }, 407 // act.OwnerAddress() is not nil,existing owner, but selfstake is not 0 408 { 409 1201000, 410 identityset.Address(27), 411 2, 412 "test2", 413 identityset.Address(10).String(), 414 identityset.Address(10).String(), 415 identityset.Address(30).String(), "1200000000000000000000000", 416 "1200000000000000000000000", 417 uint32(10000), 418 false, 419 []byte("payload"), 420 1000000, 421 1000000, 422 big.NewInt(1), 423 false, 424 nil, 425 iotextypes.ReceiptStatus_ErrCandidateAlreadyExist, 426 }, 427 // act.OwnerAddress() is not nil,existing candidate, collide with existing name,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist 428 // act.OwnerAddress() is not nil,existing candidate, collide with existing operator,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist 429 // act.OwnerAddress() is not nil,new candidate, collide with existing name 430 { 431 1201000, 432 identityset.Address(27), 433 3, 434 "test", 435 identityset.Address(10).String(), 436 identityset.Address(10).String(), 437 identityset.Address(29).String(), 438 "1200000000000000000000000", 439 "1200000000000000000000000", 440 uint32(10000), 441 false, 442 []byte("payload"), 443 1000000, 444 1000000, 445 big.NewInt(unit.Qev), 446 false, 447 nil, 448 iotextypes.ReceiptStatus_ErrCandidateConflict, 449 }, 450 // act.OwnerAddress() is not nil,new candidate, collide with existing operator 451 { 452 1201000, 453 identityset.Address(27), 454 4, 455 "test2", 456 identityset.Address(28).String(), 457 identityset.Address(10).String(), 458 identityset.Address(29).String(), 459 "1200000000000000000000000", 460 "1200000000000000000000000", 461 uint32(10000), 462 false, 463 []byte("payload"), 464 1000000, 465 1000000, 466 big.NewInt(unit.Qev), 467 false, 468 nil, 469 iotextypes.ReceiptStatus_ErrCandidateConflict, 470 }, 471 // act.OwnerAddress() is nil,existing owner, but selfstake is not 0 472 { 473 1201000, 474 identityset.Address(30), 475 1, 476 "test2", 477 identityset.Address(28).String(), 478 identityset.Address(10).String(), 479 "", 480 "1200000000000000000000000", 481 "1200000000000000000000000", 482 uint32(10000), 483 false, 484 []byte("payload"), 485 1000000, 486 1000000, 487 big.NewInt(unit.Qev), 488 false, 489 nil, 490 iotextypes.ReceiptStatus_ErrCandidateAlreadyExist, 491 }, 492 // act.OwnerAddress() is nil,existing candidate, collide with existing name,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist 493 // act.OwnerAddress() is nil,existing candidate, collide with existing operator,this case cannot happen,b/c if ownerExist,it will return ReceiptStatus_ErrCandidateAlreadyExist 494 // act.OwnerAddress() is nil,new candidate, collide with existing name 495 { 496 1201000, 497 identityset.Address(21), 498 1, 499 "test", 500 identityset.Address(28).String(), 501 identityset.Address(10).String(), 502 "", 503 "1200000000000000000000000", 504 "1200000000000000000000000", 505 uint32(10000), 506 false, 507 []byte("payload"), 508 1000000, 509 1000000, 510 big.NewInt(unit.Qev), 511 false, 512 nil, 513 iotextypes.ReceiptStatus_ErrCandidateConflict, 514 }, 515 // act.OwnerAddress() is nil,new candidate, collide with existing operator 516 { 517 1201000, 518 identityset.Address(21), 519 2, 520 "test2", 521 identityset.Address(28).String(), 522 identityset.Address(10).String(), 523 "", 524 "1200000000000000000000000", 525 "1200000000000000000000000", 526 uint32(10000), 527 false, 528 []byte("payload"), 529 1000000, 530 1000000, 531 big.NewInt(unit.Qev), 532 false, 533 nil, 534 iotextypes.ReceiptStatus_ErrCandidateConflict, 535 }, 536 // register without self-stake 537 { 538 1201000, 539 identityset.Address(27), 540 1, 541 "test", 542 identityset.Address(28).String(), 543 identityset.Address(29).String(), 544 identityset.Address(30).String(), 545 "0", 546 "0", 547 uint32(10000), 548 false, 549 nil, 550 uint64(1000000), 551 uint64(1000000), 552 big.NewInt(1), 553 true, 554 nil, 555 iotextypes.ReceiptStatus_Success, 556 }, 557 } 558 559 for _, test := range tests { 560 if test.newProtocol { 561 sm, p, _, _ = initAll(t, ctrl) 562 csr = newCandidateStateReader(sm) 563 } 564 require.NoError(setupAccount(sm, test.caller, test.initBalance)) 565 act, err := action.NewCandidateRegister(test.nonce, test.name, test.operatorAddrStr, test.rewardAddrStr, test.ownerAddrStr, test.amountStr, test.duration, test.autoStake, test.payload, test.gasLimit, test.gasPrice) 566 require.NoError(err) 567 IntrinsicGas, _ := act.IntrinsicGas() 568 ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{ 569 Caller: test.caller, 570 GasPrice: test.gasPrice, 571 IntrinsicGas: IntrinsicGas, 572 Nonce: test.nonce, 573 }) 574 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 575 BlockHeight: 1, 576 BlockTimeStamp: time.Now(), 577 GasLimit: test.blkGasLimit, 578 }) 579 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 580 g.TsunamiBlockHeight = 0 581 ctx = genesis.WithGenesisContext(ctx, g) 582 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 583 require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm))) 584 if test.err != nil { 585 continue 586 } 587 r, err := p.Handle(ctx, act, sm) 588 require.NoError(err) 589 if r != nil { 590 require.Equal(uint64(test.status), r.Status) 591 } else { 592 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 593 } 594 595 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 596 // check the special create bucket and candidate register log 597 tLogs := r.TransactionLogs() 598 if test.amountStr == "0" { 599 require.Equal(1, len(tLogs)) 600 cLog := tLogs[0] 601 require.Equal(test.caller.String(), cLog.Sender) 602 require.Equal(address.RewardingPoolAddr, cLog.Recipient) 603 require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String()) 604 } else { 605 require.Equal(2, len(tLogs)) 606 cLog := tLogs[0] 607 require.Equal(test.caller.String(), cLog.Sender) 608 require.Equal(address.StakingBucketPoolAddr, cLog.Recipient) 609 require.Equal(test.amountStr, cLog.Amount.String()) 610 611 cLog = tLogs[1] 612 require.Equal(test.caller.String(), cLog.Sender) 613 require.Equal(address.RewardingPoolAddr, cLog.Recipient) 614 require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String()) 615 } 616 617 // test candidate 618 candidate, _, err := csr.getCandidate(act.OwnerAddress()) 619 if act.OwnerAddress() == nil { 620 require.Nil(candidate) 621 require.Equal(ErrNilParameters, errors.Cause(err)) 622 candidate, _, err = csr.getCandidate(test.caller) 623 require.NoError(err) 624 require.Equal(test.caller.String(), candidate.Owner.String()) 625 } else { 626 require.NotNil(candidate) 627 require.NoError(err) 628 require.Equal(test.ownerAddrStr, candidate.Owner.String()) 629 } 630 require.Equal(test.votesStr, candidate.Votes.String()) 631 csm, err := NewCandidateStateManager(sm, false) 632 require.NoError(err) 633 candidate = csm.GetByOwner(candidate.Owner) 634 require.NotNil(candidate) 635 require.Equal(test.votesStr, candidate.Votes.String()) 636 require.Equal(test.name, candidate.Name) 637 require.Equal(test.operatorAddrStr, candidate.Operator.String()) 638 require.Equal(test.rewardAddrStr, candidate.Reward.String()) 639 require.Equal(test.amountStr, candidate.SelfStake.String()) 640 641 // test staker's account 642 caller, err := accountutil.LoadAccount(sm, test.caller) 643 require.NoError(err) 644 actCost, err := act.Cost() 645 require.NoError(err) 646 total := big.NewInt(0) 647 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, p.config.RegistrationConsts.Fee)) 648 require.Equal(test.nonce+1, caller.PendingNonce()) 649 } 650 } 651 } 652 653 func TestProtocol_HandleCandidateUpdate(t *testing.T) { 654 require := require.New(t) 655 ctrl := gomock.NewController(t) 656 tests := []struct { 657 initBalance int64 658 caller address.Address 659 nonce uint64 660 name string 661 operatorAddrStr string 662 rewardAddrStr string 663 ownerAddrStr string 664 amountStr string 665 afterUpdate string 666 duration uint32 667 autoStake bool 668 payload []byte 669 gasLimit uint64 670 blkGasLimit uint64 671 gasPrice *big.Int 672 newProtocol bool 673 // candidate update 674 updateName string 675 updateOperator string 676 updateReward string 677 err error 678 status iotextypes.ReceiptStatus 679 }{ 680 // fetchCaller ErrNotEnoughBalance 681 { 682 1200101, 683 identityset.Address(27), 684 1, 685 "test", 686 identityset.Address(28).String(), 687 identityset.Address(29).String(), 688 identityset.Address(27).String(), 689 "1200000999999999989300000", 690 "", 691 uint32(10000), 692 false, 693 []byte("payload"), 694 uint64(1000000), 695 uint64(1000000), 696 big.NewInt(1000), 697 true, 698 "update", 699 identityset.Address(31).String(), 700 identityset.Address(32).String(), 701 nil, 702 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 703 }, 704 // only owner can update candidate 705 { 706 1000, 707 identityset.Address(27), 708 1, 709 "test", 710 identityset.Address(28).String(), 711 identityset.Address(29).String(), 712 identityset.Address(30).String(), 713 "1200000000000000000000000", 714 "", 715 uint32(10000), 716 false, 717 []byte("payload"), 718 uint64(1000000), 719 uint64(1000000), 720 big.NewInt(1000), 721 true, 722 "update", 723 identityset.Address(31).String(), 724 identityset.Address(32).String(), 725 nil, 726 iotextypes.ReceiptStatus_ErrCandidateNotExist, 727 }, 728 // success,update name,operator,reward all empty 729 { 730 1201000, 731 identityset.Address(27), 732 1, 733 "test", 734 identityset.Address(28).String(), 735 identityset.Address(29).String(), 736 "", 737 "1200000000000000000000000", 738 "1806204150552640363969204", 739 uint32(10000), 740 false, 741 []byte("payload"), 742 uint64(1000000), 743 uint64(1000000), 744 big.NewInt(1000), 745 true, 746 "", 747 "", 748 "", 749 nil, 750 iotextypes.ReceiptStatus_Success, 751 }, 752 // upsert,collision 753 { 754 1201000, 755 identityset.Address(27), 756 1, 757 "test", 758 identityset.Address(29).String(), 759 identityset.Address(30).String(), 760 "", 761 "1200000000000000000000000", 762 "", 763 uint32(10000), 764 false, 765 []byte("payload"), 766 uint64(1000000), 767 uint64(1000000), 768 big.NewInt(1000), 769 true, 770 "test2", 771 "", 772 "", 773 nil, 774 iotextypes.ReceiptStatus_ErrCandidateConflict, 775 }, 776 // invalid candidate name 777 { 778 1201000, 779 identityset.Address(27), 780 1, 781 "test", 782 identityset.Address(27).String(), 783 identityset.Address(29).String(), 784 identityset.Address(27).String(), 785 "1200000000000000000000000", 786 "1806204150552640363969204", 787 uint32(10000), 788 false, 789 []byte("payload"), 790 uint64(1000000), 791 uint64(1000000), 792 big.NewInt(1000), 793 true, 794 "!invalidname", 795 identityset.Address(31).String(), 796 identityset.Address(32).String(), 797 action.ErrInvalidCanName, 798 iotextypes.ReceiptStatus_Failure, 799 }, 800 // success,update name, operator and reward address 801 { 802 1201000, 803 identityset.Address(27), 804 1, 805 "test", 806 identityset.Address(27).String(), 807 identityset.Address(29).String(), 808 identityset.Address(27).String(), 809 "1200000000000000000000000", 810 "1806204150552640363969204", 811 uint32(10000), 812 false, 813 []byte("payload"), 814 uint64(1000000), 815 uint64(1000000), 816 big.NewInt(1000), 817 true, 818 "update", 819 identityset.Address(31).String(), 820 identityset.Address(32).String(), 821 nil, 822 iotextypes.ReceiptStatus_Success, 823 }, 824 // upsert,collide with candidate name 825 { 826 1201000, 827 identityset.Address(27), 828 uint64(1), 829 "test", 830 identityset.Address(28).String(), 831 identityset.Address(29).String(), 832 identityset.Address(27).String(), 833 "1200000000000000000000000", 834 "", 835 uint32(10000), 836 true, 837 []byte("payload"), 838 uint64(1000000), 839 uint64(1000000), 840 big.NewInt(1000), 841 false, 842 "test1", 843 "", 844 "", 845 nil, 846 iotextypes.ReceiptStatus_ErrCandidateConflict, 847 }, 848 // upsert,collide with operator address 849 { 850 1201000, 851 identityset.Address(27), 852 uint64(1), 853 "test", 854 identityset.Address(28).String(), 855 identityset.Address(29).String(), 856 identityset.Address(27).String(), 857 "1200000000000000000000000", 858 "", 859 uint32(10000), 860 true, 861 []byte("payload"), 862 uint64(1000000), 863 uint64(1000000), 864 big.NewInt(1000), 865 false, 866 "test", 867 identityset.Address(7).String(), 868 "", 869 nil, 870 iotextypes.ReceiptStatus_ErrCandidateConflict, 871 }, 872 } 873 874 for _, test := range tests { 875 sm, p, _, _ := initAll(t, ctrl) 876 csr := newCandidateStateReader(sm) 877 require.NoError(setupAccount(sm, test.caller, test.initBalance)) 878 act, err := action.NewCandidateRegister(test.nonce, test.name, test.operatorAddrStr, test.rewardAddrStr, test.ownerAddrStr, test.amountStr, test.duration, test.autoStake, test.payload, test.gasLimit, test.gasPrice) 879 require.NoError(err) 880 intrinsic, _ := act.IntrinsicGas() 881 ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{ 882 Caller: test.caller, 883 GasPrice: test.gasPrice, 884 IntrinsicGas: intrinsic, 885 Nonce: test.nonce, 886 }) 887 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 888 BlockHeight: 1, 889 BlockTimeStamp: time.Now(), 890 GasLimit: test.blkGasLimit, 891 }) 892 ctx = genesis.WithGenesisContext(ctx, genesis.Default) 893 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 894 _, err = p.Handle(ctx, act, sm) 895 require.NoError(err) 896 897 cu, err := action.NewCandidateUpdate(test.nonce+1, test.updateName, test.updateOperator, test.updateReward, test.gasLimit, test.gasPrice) 898 require.NoError(err) 899 intrinsic, _ = cu.IntrinsicGas() 900 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 901 Caller: test.caller, 902 GasPrice: test.gasPrice, 903 IntrinsicGas: intrinsic, 904 Nonce: test.nonce + 1, 905 }) 906 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 907 BlockHeight: 1, 908 BlockTimeStamp: time.Now(), 909 GasLimit: test.blkGasLimit, 910 }) 911 require.Equal(test.err, errors.Cause(p.Validate(ctx, cu, sm))) 912 if test.err != nil { 913 continue 914 } 915 r, err := p.Handle(ctx, cu, sm) 916 require.NoError(err) 917 if r != nil { 918 require.Equal(uint64(test.status), r.Status) 919 } else { 920 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 921 } 922 923 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 924 // test candidate 925 candidate, _, err := csr.getCandidate(act.OwnerAddress()) 926 if act.OwnerAddress() == nil { 927 require.Nil(candidate) 928 require.Equal(ErrNilParameters, errors.Cause(err)) 929 candidate, _, err = csr.getCandidate(test.caller) 930 require.NoError(err) 931 require.Equal(test.caller.String(), candidate.Owner.String()) 932 } else { 933 require.NotNil(candidate) 934 require.NoError(err) 935 require.Equal(test.ownerAddrStr, candidate.Owner.String()) 936 } 937 require.Equal(test.afterUpdate, candidate.Votes.String()) 938 csm, err := NewCandidateStateManager(sm, false) 939 require.NoError(err) 940 candidate = csm.GetByOwner(candidate.Owner) 941 require.NotNil(candidate) 942 require.Equal(test.afterUpdate, candidate.Votes.String()) 943 if test.updateName != "" { 944 require.Equal(test.updateName, candidate.Name) 945 } 946 if test.updateOperator != "" { 947 require.Equal(test.updateOperator, candidate.Operator.String()) 948 } 949 if test.updateOperator != "" { 950 require.Equal(test.updateReward, candidate.Reward.String()) 951 } 952 require.Equal(test.afterUpdate, candidate.Votes.String()) 953 require.Equal(test.amountStr, candidate.SelfStake.String()) 954 955 // test staker's account 956 caller, err := accountutil.LoadAccount(sm, test.caller) 957 require.NoError(err) 958 actCost, err := act.Cost() 959 require.NoError(err) 960 cuCost, err := cu.Cost() 961 require.NoError(err) 962 total := big.NewInt(0) 963 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, cuCost).Add(total, p.config.RegistrationConsts.Fee)) 964 require.Equal(test.nonce+2, caller.PendingNonce()) 965 } 966 } 967 } 968 969 func TestProtocol_HandleUnstake(t *testing.T) { 970 require := require.New(t) 971 ctrl := gomock.NewController(t) 972 973 sm, p, candidate, candidate2 := initAll(t, ctrl) 974 csr := newCandidateStateReader(sm) 975 initCreateStake(t, sm, identityset.Address(2), 100, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, "100000000000000000000", false) 976 977 callerAddr := identityset.Address(1) 978 tests := []struct { 979 // creat stake fields 980 caller address.Address 981 amount string 982 autoStake bool 983 afterUnstake string 984 initBalance int64 985 selfstaking bool 986 // action fields 987 index uint64 988 // block context 989 blkTimestamp time.Time 990 ctxTimestamp time.Time 991 // clear flag for inMemCandidates 992 clear bool 993 // need new p 994 newProtocol bool 995 err error 996 // expected result 997 status iotextypes.ReceiptStatus 998 }{ 999 // fetchCaller ErrNotEnoughBalance 1000 { 1001 callerAddr, 1002 "100990000000000000000", 1003 false, 1004 "", 1005 101, 1006 false, 1007 0, 1008 time.Now(), 1009 time.Now(), 1010 false, 1011 true, 1012 nil, 1013 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 1014 }, 1015 // fetchBucket, bucket.Owner is not equal to actionCtx.Caller 1016 { 1017 identityset.Address(12), 1018 "100000000000000000000", 1019 false, 1020 "", 1021 101, 1022 false, 1023 0, 1024 time.Now(), 1025 time.Now(), 1026 false, 1027 false, 1028 nil, 1029 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 1030 }, 1031 // fetchBucket,ReceiptStatus_ErrInvalidBucketType cannot happen,because allowSelfStaking is true 1032 // fetchBucket and updateBucket call getbucket, ReceiptStatus_ErrInvalidBucketIndex 1033 { 1034 identityset.Address(33), 1035 "100000000000000000000", 1036 false, 1037 "", 1038 101, 1039 false, 1040 1, 1041 time.Now(), 1042 time.Now(), 1043 false, 1044 true, 1045 nil, 1046 iotextypes.ReceiptStatus_ErrInvalidBucketIndex, 1047 }, 1048 // inMemCandidates.GetByOwner,ErrInvalidOwner 1049 { 1050 callerAddr, 1051 "100000000000000000000", 1052 false, 1053 "", 1054 101, 1055 false, 1056 0, 1057 time.Now(), 1058 time.Now(), 1059 true, 1060 true, 1061 nil, 1062 iotextypes.ReceiptStatus_ErrCandidateNotExist, 1063 }, 1064 // unstake before maturity 1065 { 1066 callerAddr, 1067 "100000000000000000000", 1068 false, 1069 "", 1070 101, 1071 false, 1072 0, 1073 time.Now(), 1074 time.Now(), 1075 false, 1076 true, 1077 nil, 1078 iotextypes.ReceiptStatus_ErrUnstakeBeforeMaturity, 1079 }, 1080 // unstake with autoStake bucket 1081 { 1082 callerAddr, 1083 "100000000000000000000", 1084 true, 1085 "0", 1086 101, 1087 false, 1088 0, 1089 time.Now(), 1090 time.Now().Add(time.Duration(1) * 24 * time.Hour), 1091 false, 1092 true, 1093 nil, 1094 iotextypes.ReceiptStatus_ErrInvalidBucketType, 1095 }, 1096 // Upsert error cannot happen,because collision cannot happen 1097 // success 1098 { 1099 callerAddr, 1100 "100000000000000000000", 1101 false, 1102 "0", 1103 101, 1104 false, 1105 0, 1106 time.Now(), 1107 time.Now().Add(time.Duration(1) * 24 * time.Hour), 1108 false, 1109 true, 1110 nil, 1111 iotextypes.ReceiptStatus_Success, 1112 }, 1113 } 1114 1115 var ( 1116 ctx context.Context 1117 gasPrice = big.NewInt(unit.Qev) 1118 gasLimit uint64 = 10000 1119 nonce uint64 = 1 1120 blkHeight uint64 = 1 1121 ) 1122 1123 for _, test := range tests { 1124 if test.newProtocol { 1125 sm, p, candidate, _ = initAll(t, ctrl) 1126 } else { 1127 candidate = candidate2 1128 } 1129 1130 var createCost *big.Int 1131 ctx, createCost = initCreateStake(t, sm, test.caller, test.initBalance, big.NewInt(unit.Qev), gasLimit, nonce, blkHeight, test.blkTimestamp, gasLimit, p, candidate, test.amount, test.autoStake) 1132 act, err := action.NewUnstake(nonce+1, test.index, nil, gasLimit, gasPrice) 1133 require.NoError(err) 1134 intrinsicGas, err := act.IntrinsicGas() 1135 require.NoError(err) 1136 if test.blkTimestamp != test.ctxTimestamp { 1137 blkCtx := protocol.MustGetBlockCtx(ctx) 1138 blkCtx.BlockTimeStamp = test.ctxTimestamp 1139 ctx = protocol.WithBlockCtx(ctx, blkCtx) 1140 } 1141 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1142 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1143 Caller: test.caller, 1144 GasPrice: gasPrice, 1145 IntrinsicGas: intrinsicGas, 1146 Nonce: nonce + 1, 1147 }) 1148 var r *action.Receipt 1149 if test.clear { 1150 csm, err := NewCandidateStateManager(sm, false) 1151 require.NoError(err) 1152 sc, ok := csm.(*candSM) 1153 require.True(ok) 1154 sc.candCenter.deleteForTestOnly(test.caller) 1155 require.False(csm.ContainsOwner(test.caller)) 1156 r, err = p.handle(ctx, act, csm) 1157 require.Equal(test.err, errors.Cause(err)) 1158 } else { 1159 r, err = p.Handle(ctx, act, sm) 1160 require.Equal(test.err, errors.Cause(err)) 1161 } 1162 if r != nil { 1163 require.Equal(uint64(test.status), r.Status) 1164 } else { 1165 require.Equal(test.status, iotextypes.ReceiptStatus_Success) 1166 } 1167 1168 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 1169 // test bucket index and bucket 1170 csr = newCandidateStateReader(sm) 1171 bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) 1172 require.NoError(err) 1173 require.Equal(1, len(*bucketIndices)) 1174 bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) 1175 require.NoError(err) 1176 require.Equal(1, len(*bucketIndices)) 1177 indices := *bucketIndices 1178 bucket, err := csr.getBucket(indices[0]) 1179 require.NoError(err) 1180 require.Equal(candidate.Owner.String(), bucket.Candidate.String()) 1181 require.Equal(test.caller.String(), bucket.Owner.String()) 1182 require.Equal(test.amount, bucket.StakedAmount.String()) 1183 1184 // test candidate 1185 candidate, _, err = csr.getCandidate(candidate.Owner) 1186 require.NoError(err) 1187 require.Equal(test.afterUnstake, candidate.Votes.String()) 1188 csm, err := NewCandidateStateManager(sm, false) 1189 require.NoError(err) 1190 candidate = csm.GetByOwner(candidate.Owner) 1191 require.NotNil(candidate) 1192 require.Equal(test.afterUnstake, candidate.Votes.String()) 1193 1194 // test staker's account 1195 caller, err := accountutil.LoadAccount(sm, test.caller) 1196 require.NoError(err) 1197 actCost, err := act.Cost() 1198 require.NoError(err) 1199 require.Equal(nonce+2, caller.PendingNonce()) 1200 total := big.NewInt(0) 1201 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost)) 1202 } 1203 } 1204 1205 // verify bucket unstaked 1206 vb, err := csr.getBucket(0) 1207 require.NoError(err) 1208 require.True(vb.isUnstaked()) 1209 1210 unstake, err := action.NewUnstake(nonce+2, 0, nil, gasLimit, gasPrice) 1211 require.NoError(err) 1212 changeCand, err := action.NewChangeCandidate(nonce+2, candidate2.Name, 0, nil, gasLimit, gasPrice) 1213 require.NoError(err) 1214 deposit, err := action.NewDepositToStake(nonce+2, 0, "10000", nil, gasLimit, gasPrice) 1215 require.NoError(err) 1216 restake, err := action.NewRestake(nonce+2, 0, 0, false, nil, gasLimit, gasPrice) 1217 require.NoError(err) 1218 1219 unstakedBucketTests := []struct { 1220 act action.Action 1221 greenland bool 1222 status iotextypes.ReceiptStatus 1223 }{ 1224 // unstake an already unstaked bucket again not allowed 1225 {unstake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType}, 1226 // change candidate for an unstaked bucket not allowed 1227 {changeCand, true, iotextypes.ReceiptStatus_ErrInvalidBucketType}, 1228 // deposit to unstaked bucket not allowed 1229 {deposit, true, iotextypes.ReceiptStatus_ErrInvalidBucketType}, 1230 // restake an unstaked bucket not allowed 1231 {restake, true, iotextypes.ReceiptStatus_ErrInvalidBucketType}, 1232 // restake an unstaked bucket is allowed pre-Greenland 1233 {restake, false, iotextypes.ReceiptStatus_ErrNotEnoughBalance}, 1234 } 1235 for i, v := range unstakedBucketTests { 1236 greenland := genesis.Default 1237 if v.greenland { 1238 blkCtx := protocol.MustGetBlockCtx(ctx) 1239 greenland.GreenlandBlockHeight = blkCtx.BlockHeight 1240 } 1241 actCtx := protocol.MustGetActionCtx(ctx) 1242 actCtx.Nonce = nonce + 2 + uint64(i) 1243 ctx = protocol.WithActionCtx(ctx, actCtx) 1244 ctx = genesis.WithGenesisContext(ctx, greenland) 1245 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1246 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1247 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1248 Caller: callerAddr, 1249 GasPrice: gasPrice, 1250 Nonce: nonce + 2 + uint64(i), 1251 }) 1252 _, err = p.Start(ctx, sm) 1253 require.NoError(err) 1254 r, err := p.Handle(ctx, v.act, sm) 1255 require.NoError(err) 1256 require.EqualValues(v.status, r.Status) 1257 1258 if !v.greenland { 1259 // pre-Greenland allows restaking an unstaked bucket, and it is considered staked afterwards 1260 vb, err := csr.getBucket(0) 1261 require.NoError(err) 1262 require.True(vb.StakeStartTime.Unix() != 0) 1263 require.True(vb.UnstakeStartTime.Unix() != 0) 1264 require.False(vb.isUnstaked()) 1265 } 1266 } 1267 } 1268 1269 func TestProtocol_HandleWithdrawStake(t *testing.T) { 1270 require := require.New(t) 1271 ctrl := gomock.NewController(t) 1272 tests := []struct { 1273 // create stake fields 1274 amount string 1275 initBalance int64 1276 selfstaking bool 1277 // block context 1278 blkTimestamp time.Time 1279 ctxTimestamp time.Time 1280 // if unstake 1281 unstake bool 1282 // withdraw fields 1283 withdrawIndex uint64 1284 // expected result 1285 status iotextypes.ReceiptStatus 1286 }{ 1287 // fetchCaller ErrNotEnoughBalance 1288 { 1289 "100990000000000000000", 1290 101, 1291 false, 1292 time.Now(), 1293 time.Now(), 1294 true, 1295 0, 1296 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 1297 }, 1298 // fetchBucket ReceiptStatus_ErrInvalidBucketIndex 1299 { 1300 "100000000000000000000", 1301 101, 1302 false, 1303 time.Now(), 1304 time.Now(), 1305 true, 1306 1, 1307 iotextypes.ReceiptStatus_ErrInvalidBucketIndex, 1308 }, 1309 // check unstake time,ReceiptStatus_ErrWithdrawBeforeUnstake 1310 { 1311 "100000000000000000000", 1312 101, 1313 false, 1314 time.Now(), 1315 time.Now(), 1316 false, 1317 0, 1318 iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake, 1319 }, 1320 // check ReceiptStatus_ErrWithdrawBeforeMaturity 1321 { 1322 "100000000000000000000", 1323 101, 1324 false, 1325 time.Now(), 1326 time.Now(), 1327 true, 1328 0, 1329 iotextypes.ReceiptStatus_ErrWithdrawBeforeMaturity, 1330 }, 1331 // delxxx cannot happen,because unstake first called without error 1332 // ReceiptStatus_Success 1333 { 1334 "100000000000000000000", 1335 101, 1336 false, 1337 time.Now(), 1338 time.Now().Add(time.Hour * 500), 1339 true, 1340 0, 1341 iotextypes.ReceiptStatus_Success, 1342 }, 1343 } 1344 1345 for _, test := range tests { 1346 sm, p, _, candidate := initAll(t, ctrl) 1347 csr := newCandidateStateReader(sm) 1348 caller := identityset.Address(2) 1349 require.NoError(setupAccount(sm, caller, test.initBalance)) 1350 gasPrice := big.NewInt(unit.Qev) 1351 gasLimit := uint64(10000) 1352 ctx, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), gasLimit, 1, 1, test.blkTimestamp, gasLimit, p, candidate, test.amount, false) 1353 var actCost *big.Int 1354 if test.unstake { 1355 act, err := action.NewUnstake(1, 0, nil, gasLimit, big.NewInt(unit.Qev)) 1356 require.NoError(err) 1357 intrinsic, err := act.IntrinsicGas() 1358 require.NoError(err) 1359 actCost, err = act.Cost() 1360 require.NoError(err) 1361 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1362 Caller: caller, 1363 GasPrice: gasPrice, 1364 IntrinsicGas: intrinsic, 1365 Nonce: 2, 1366 }) 1367 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1368 BlockHeight: 1, 1369 BlockTimeStamp: time.Now().Add(time.Duration(1) * 24 * time.Hour), 1370 GasLimit: 1000000, 1371 }) 1372 1373 _, err = p.Handle(ctx, act, sm) 1374 require.NoError(err) 1375 require.NoError(p.Commit(ctx, sm)) 1376 require.Equal(0, sm.Snapshot()) 1377 } 1378 1379 withdraw, err := action.NewWithdrawStake(1, test.withdrawIndex, 1380 nil, gasLimit, gasPrice) 1381 require.NoError(err) 1382 actionCtx := protocol.MustGetActionCtx(ctx) 1383 blkCtx := protocol.MustGetBlockCtx(ctx) 1384 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1385 Caller: actionCtx.Caller, 1386 GasPrice: actionCtx.GasPrice, 1387 IntrinsicGas: actionCtx.IntrinsicGas, 1388 Nonce: actionCtx.Nonce + 1, 1389 }) 1390 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1391 BlockHeight: blkCtx.BlockHeight, 1392 BlockTimeStamp: test.ctxTimestamp, 1393 GasLimit: blkCtx.GasLimit, 1394 }) 1395 r, err := p.Handle(ctx, withdraw, sm) 1396 require.NoError(err) 1397 if r != nil { 1398 require.Equal(uint64(test.status), r.Status) 1399 } else { 1400 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 1401 } 1402 1403 if test.status == iotextypes.ReceiptStatus_Success { 1404 // check the special withdraw bucket log 1405 tLogs := r.TransactionLogs() 1406 require.Equal(1, len(tLogs)) 1407 wLog := tLogs[0] 1408 require.Equal(address.StakingBucketPoolAddr, wLog.Sender) 1409 require.Equal(caller.String(), wLog.Recipient) 1410 require.Equal(test.amount, wLog.Amount.String()) 1411 1412 // test bucket index and bucket 1413 _, _, err := csr.candBucketIndices(candidate.Owner) 1414 require.Error(err) 1415 _, _, err = csr.voterBucketIndices(candidate.Owner) 1416 require.Error(err) 1417 1418 // test staker's account 1419 caller, err := accountutil.LoadAccount(sm, caller) 1420 require.NoError(err) 1421 withdrawCost, err := withdraw.Cost() 1422 require.NoError(err) 1423 require.Equal(uint64(4), caller.PendingNonce()) 1424 total := big.NewInt(0) 1425 withdrawAmount, ok := new(big.Int).SetString(test.amount, 10) 1426 require.True(ok) 1427 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, withdrawCost).Add(total, createCost).Sub(total, withdrawAmount)) 1428 } 1429 } 1430 } 1431 1432 func TestProtocol_HandleChangeCandidate(t *testing.T) { 1433 require := require.New(t) 1434 ctrl := gomock.NewController(t) 1435 1436 tests := []struct { 1437 // creat stake fields 1438 caller address.Address 1439 amount string 1440 afterChange string 1441 afterChangeSelfStake string 1442 initBalance int64 1443 selfstaking bool 1444 // action fields 1445 index uint64 1446 candidateName string 1447 gasPrice *big.Int 1448 gasLimit uint64 1449 nonce uint64 1450 // block context 1451 blkHeight uint64 1452 blkTimestamp time.Time 1453 blkGasLimit uint64 1454 // clear flag for inMemCandidates 1455 clear bool 1456 // expected result 1457 err error 1458 status iotextypes.ReceiptStatus 1459 }{ 1460 // fetchCaller ReceiptStatus_ErrNotEnoughBalance 1461 { 1462 identityset.Address(1), 1463 "100990000000000000000", 1464 "0", 1465 "0", 1466 101, 1467 false, 1468 1, 1469 "test2", 1470 big.NewInt(unit.Qev), 1471 10000, 1472 1, 1473 1, 1474 time.Now(), 1475 10000, 1476 true, 1477 nil, 1478 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 1479 }, 1480 // ReceiptStatus_ErrCandidateNotExist 1481 { 1482 identityset.Address(1), 1483 "100000000000000000000", 1484 "0", 1485 "0", 1486 101, 1487 false, 1488 1, 1489 "testname", 1490 big.NewInt(unit.Qev), 1491 10000, 1492 1, 1493 1, 1494 time.Now(), 1495 10000, 1496 true, 1497 nil, 1498 iotextypes.ReceiptStatus_ErrCandidateNotExist, 1499 }, 1500 // fetchBucket,ReceiptStatus_ErrInvalidBucketType 1501 { 1502 identityset.Address(1), 1503 "100000000000000000000", 1504 "0", 1505 "0", 1506 101, 1507 true, 1508 1, 1509 "test2", 1510 big.NewInt(unit.Qev), 1511 10000, 1512 1, 1513 1, 1514 time.Now(), 1515 10000, 1516 false, 1517 nil, 1518 iotextypes.ReceiptStatus_ErrInvalidBucketType, 1519 }, 1520 // ErrInvalidOwner 1521 { 1522 identityset.Address(1), 1523 "100000000000000000000", 1524 "0", 1525 "0", 1526 101, 1527 false, 1528 1, 1529 "test2", 1530 big.NewInt(unit.Qev), 1531 10000, 1532 1, 1533 1, 1534 time.Now(), 1535 10000, 1536 true, 1537 nil, 1538 iotextypes.ReceiptStatus_ErrCandidateNotExist, 1539 }, 1540 // invalid candidate name 1541 { 1542 identityset.Address(2), 1543 "100000000000000000000", 1544 "200000000000000000000", 1545 "1200000000000000000000000", 1546 101, 1547 false, 1548 0, 1549 "~1", 1550 big.NewInt(unit.Qev), 1551 10000, 1552 1, 1553 1, 1554 time.Now(), 1555 10000, 1556 false, 1557 action.ErrInvalidCanName, 1558 iotextypes.ReceiptStatus_Failure, 1559 }, 1560 // invalid candidate name 2 1561 { 1562 identityset.Address(2), 1563 "100000000000000000000", 1564 "200000000000000000000", 1565 "1200000000000000000000000", 1566 101, 1567 false, 1568 0, 1569 "0123456789abc", 1570 big.NewInt(unit.Qev), 1571 10000, 1572 1, 1573 1, 1574 time.Now(), 1575 10000, 1576 false, 1577 action.ErrInvalidCanName, 1578 iotextypes.ReceiptStatus_Failure, 1579 }, 1580 // invalid candidate name 3 1581 { 1582 identityset.Address(2), 1583 "100000000000000000000", 1584 "200000000000000000000", 1585 "1200000000000000000000000", 1586 101, 1587 false, 1588 0, 1589 "", 1590 big.NewInt(unit.Qev), 1591 10000, 1592 1, 1593 1, 1594 time.Now(), 1595 10000, 1596 false, 1597 action.ErrInvalidCanName, 1598 iotextypes.ReceiptStatus_Failure, 1599 }, 1600 // Upsert error cannot happen,because CreateStake already check collision 1601 // change from 0 to test1 1602 { 1603 identityset.Address(2), 1604 "100000000000000000000", 1605 "200000000000000000000", 1606 "1200000000000000000000000", 1607 101, 1608 false, 1609 0, 1610 "test1", 1611 big.NewInt(unit.Qev), 1612 10000, 1613 1, 1614 1, 1615 time.Now(), 1616 10000, 1617 false, 1618 nil, 1619 iotextypes.ReceiptStatus_Success, 1620 }, 1621 // test change to same candidate (Hawaii fix) 1622 { 1623 identityset.Address(2), 1624 "100000000000000000000", 1625 "100000000000000000000", 1626 "1200000000000000000000000", 1627 101, 1628 false, 1629 0, 1630 "test2", 1631 big.NewInt(unit.Qev), 1632 10000, 1633 1, 1634 genesis.Default.HawaiiBlockHeight, 1635 time.Now(), 1636 10000, 1637 false, 1638 nil, 1639 iotextypes.ReceiptStatus_ErrCandidateAlreadyExist, 1640 }, 1641 } 1642 1643 for _, test := range tests { 1644 sm, p, candidate, candidate2 := initAll(t, ctrl) 1645 csr := newCandidateStateReader(sm) 1646 // candidate2 vote self,index 0 1647 ctx, _ := initCreateStake(t, sm, candidate2.Owner, 101, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, "100000000000000000000", false) 1648 // candidate vote self,index 1 1649 _, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), test.gasLimit, test.nonce, test.blkHeight, test.blkTimestamp, test.blkGasLimit, p, candidate, test.amount, false) 1650 1651 act, err := action.NewChangeCandidate(test.nonce+1, test.candidateName, test.index, nil, test.gasLimit, test.gasPrice) 1652 require.NoError(err) 1653 intrinsic, err := act.IntrinsicGas() 1654 require.NoError(err) 1655 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1656 Caller: test.caller, 1657 GasPrice: test.gasPrice, 1658 IntrinsicGas: intrinsic, 1659 Nonce: test.nonce + 1, 1660 }) 1661 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1662 BlockHeight: test.blkHeight, 1663 BlockTimeStamp: time.Now(), 1664 GasLimit: 1000000, 1665 }) 1666 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1667 var r *action.Receipt 1668 if test.clear { 1669 csm, err := NewCandidateStateManager(sm, false) 1670 require.NoError(err) 1671 sc, ok := csm.(*candSM) 1672 require.True(ok) 1673 cc := sc.candCenter.GetBySelfStakingIndex(test.index) 1674 sc.candCenter.deleteForTestOnly(cc.Owner) 1675 require.False(csm.ContainsOwner(cc.Owner)) 1676 r, err = p.handle(ctx, act, csm) 1677 require.Equal(test.err, errors.Cause(err)) 1678 } else { 1679 require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm))) 1680 if test.err != nil { 1681 continue 1682 } 1683 r, err = p.Handle(ctx, act, sm) 1684 require.NoError(err) 1685 } 1686 if r != nil { 1687 require.Equal(uint64(test.status), r.Status) 1688 } else { 1689 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 1690 } 1691 1692 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 1693 // test bucket index and bucket 1694 bucketIndices, _, err := csr.candBucketIndices(identityset.Address(1)) 1695 require.NoError(err) 1696 require.Equal(2, len(*bucketIndices)) 1697 bucketIndices, _, err = csr.voterBucketIndices(identityset.Address(1)) 1698 require.NoError(err) 1699 require.Equal(1, len(*bucketIndices)) 1700 indices := *bucketIndices 1701 bucket, err := csr.getBucket(indices[0]) 1702 require.NoError(err) 1703 require.Equal(identityset.Address(1).String(), bucket.Candidate.String()) 1704 require.Equal(identityset.Address(1).String(), bucket.Owner.String()) 1705 require.Equal(test.amount, bucket.StakedAmount.String()) 1706 1707 // test candidate 1708 candidate, _, err := csr.getCandidate(candidate.Owner) 1709 require.NotNil(candidate) 1710 require.NoError(err) 1711 require.Equal(test.afterChange, candidate.Votes.String()) 1712 require.Equal(test.candidateName, candidate.Name) 1713 require.Equal(candidate.Operator.String(), candidate.Operator.String()) 1714 require.Equal(candidate.Reward.String(), candidate.Reward.String()) 1715 require.Equal(candidate.Owner.String(), candidate.Owner.String()) 1716 require.Equal(test.afterChangeSelfStake, candidate.SelfStake.String()) 1717 csm, err := NewCandidateStateManager(sm, false) 1718 require.NoError(err) 1719 candidate = csm.GetByOwner(candidate.Owner) 1720 require.NotNil(candidate) 1721 require.Equal(test.afterChange, candidate.Votes.String()) 1722 1723 // test staker's account 1724 caller, err := accountutil.LoadAccount(sm, test.caller) 1725 require.NoError(err) 1726 actCost, err := act.Cost() 1727 require.NoError(err) 1728 require.Equal(test.nonce+2, caller.PendingNonce()) 1729 total := big.NewInt(0) 1730 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost)) 1731 } 1732 } 1733 } 1734 1735 func TestProtocol_HandleChangeCandidate_ClearPrevCandidateSelfStake(t *testing.T) { 1736 r := require.New(t) 1737 ctrl := gomock.NewController(t) 1738 t.Run("clear if bucket is an expired endorse bucket", func(t *testing.T) { 1739 sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, 1740 []*bucketConfig{ 1741 {identityset.Address(1), identityset.Address(5), "100000000000000000000", 1, true, true, nil, 1}, 1742 }, 1743 []*candidateConfig{ 1744 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 1745 {identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"}, 1746 }, 1) 1747 r.NoError(setupAccount(sm, identityset.Address(5), 10000)) 1748 nonce := uint64(1) 1749 act, err := action.NewChangeCandidate(nonce, "test2", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 1750 r.NoError(err) 1751 intrinsic, err := act.IntrinsicGas() 1752 r.NoError(err) 1753 ctx := context.Background() 1754 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 1755 g.TsunamiBlockHeight = 0 1756 ctx = genesis.WithGenesisContext(ctx, g) 1757 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1758 Caller: identityset.Address(5), 1759 GasPrice: big.NewInt(unit.Qev), 1760 IntrinsicGas: intrinsic, 1761 Nonce: nonce, 1762 }) 1763 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1764 BlockHeight: 2, 1765 BlockTimeStamp: time.Now(), 1766 GasLimit: 1000000, 1767 }) 1768 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1769 recipt, err := p.Handle(ctx, act, sm) 1770 r.NoError(err) 1771 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 1772 // test previous candidate self stake 1773 csm, err := NewCandidateStateManager(sm, false) 1774 r.NoError(err) 1775 prevCand := csm.GetByOwner(identityset.Address(1)) 1776 r.Equal("0", prevCand.SelfStake.String()) 1777 r.EqualValues(uint64(candidateNoSelfStakeBucketIndex), prevCand.SelfStakeBucketIdx) 1778 r.Equal("0", prevCand.Votes.String()) 1779 }) 1780 t.Run("not clear if bucket is a vote bucket", func(t *testing.T) { 1781 sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, 1782 []*bucketConfig{ 1783 {identityset.Address(1), identityset.Address(1), "120000000000000000000", 1, true, true, nil, 0}, 1784 {identityset.Address(2), identityset.Address(2), "120000000000000000000", 1, true, true, nil, 0}, 1785 {identityset.Address(1), identityset.Address(1), "100000000000000000000", 1, true, false, nil, 0}, 1786 }, 1787 []*candidateConfig{ 1788 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 1789 {identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"}, 1790 }, 1) 1791 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 1792 nonce := uint64(1) 1793 act, err := action.NewChangeCandidate(nonce, "test2", buckets[2].Index, nil, 10000, big.NewInt(unit.Qev)) 1794 r.NoError(err) 1795 intrinsic, err := act.IntrinsicGas() 1796 r.NoError(err) 1797 ctx := context.Background() 1798 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 1799 g.TsunamiBlockHeight = 0 1800 ctx = genesis.WithGenesisContext(ctx, g) 1801 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1802 Caller: identityset.Address(1), 1803 GasPrice: big.NewInt(unit.Qev), 1804 IntrinsicGas: intrinsic, 1805 Nonce: nonce, 1806 }) 1807 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1808 BlockHeight: 2, 1809 BlockTimeStamp: time.Now(), 1810 GasLimit: 1000000, 1811 }) 1812 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1813 recipt, err := p.Handle(ctx, act, sm) 1814 r.NoError(err) 1815 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 1816 // test previous candidate self stake 1817 csm, err := NewCandidateStateManager(sm, false) 1818 r.NoError(err) 1819 prevCand := csm.GetByOwner(buckets[2].Candidate) 1820 r.Equal("120000000000000000000", prevCand.SelfStake.String()) 1821 r.EqualValues(0, prevCand.SelfStakeBucketIdx) 1822 r.Equal("124562140820308711042", prevCand.Votes.String()) 1823 }) 1824 t.Run("not clear if bucket is a vote bucket", func(t *testing.T) { 1825 sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, 1826 []*bucketConfig{ 1827 {identityset.Address(1), identityset.Address(1), "120000000000000000000", 1, true, true, nil, 0}, 1828 {identityset.Address(2), identityset.Address(2), "120000000000000000000", 1, true, true, nil, 0}, 1829 {identityset.Address(1), identityset.Address(1), "100000000000000000000", 1, true, false, nil, 0}, 1830 }, 1831 []*candidateConfig{ 1832 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 1833 {identityset.Address(2), identityset.Address(12), identityset.Address(22), "test2"}, 1834 }, 1) 1835 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 1836 nonce := uint64(1) 1837 act, err := action.NewChangeCandidate(nonce, "test2", buckets[2].Index, nil, 10000, big.NewInt(unit.Qev)) 1838 r.NoError(err) 1839 intrinsic, err := act.IntrinsicGas() 1840 r.NoError(err) 1841 ctx := context.Background() 1842 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 1843 g.TsunamiBlockHeight = 0 1844 ctx = genesis.WithGenesisContext(ctx, g) 1845 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 1846 Caller: identityset.Address(1), 1847 GasPrice: big.NewInt(unit.Qev), 1848 IntrinsicGas: intrinsic, 1849 Nonce: nonce, 1850 }) 1851 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 1852 BlockHeight: 2, 1853 BlockTimeStamp: time.Now(), 1854 GasLimit: 1000000, 1855 }) 1856 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 1857 recipt, err := p.Handle(ctx, act, sm) 1858 r.NoError(err) 1859 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 1860 // test previous candidate self stake 1861 csm, err := NewCandidateStateManager(sm, false) 1862 r.NoError(err) 1863 prevCand := csm.GetByOwner(buckets[2].Candidate) 1864 r.Equal("120000000000000000000", prevCand.SelfStake.String()) 1865 r.EqualValues(0, prevCand.SelfStakeBucketIdx) 1866 r.Equal("124562140820308711042", prevCand.Votes.String()) 1867 }) 1868 } 1869 1870 func TestProtocol_HandleTransferStake(t *testing.T) { 1871 require := require.New(t) 1872 ctrl := gomock.NewController(t) 1873 1874 tests := []struct { 1875 // creat stake fields 1876 caller address.Address 1877 amount string 1878 afterTransfer uint64 1879 initBalance int64 1880 // action fields 1881 index uint64 1882 gasPrice *big.Int 1883 gasLimit uint64 1884 nonce uint64 1885 // block context 1886 blkHeight uint64 1887 blkTimestamp time.Time 1888 blkGasLimit uint64 1889 // NewTransferStake fields 1890 to address.Address 1891 toInitBalance uint64 1892 init bool 1893 // expected result 1894 err error 1895 status iotextypes.ReceiptStatus 1896 }{ 1897 // fetchCaller ReceiptStatus_ErrNotEnoughBalance 1898 { 1899 identityset.Address(2), 1900 "100990000000000000000", 1901 0, 1902 101, 1903 0, 1904 big.NewInt(unit.Qev), 1905 1000000000, 1906 2, 1907 1, 1908 time.Now(), 1909 10000, 1910 identityset.Address(1), 1911 1, 1912 false, 1913 nil, 1914 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 1915 }, 1916 // fetchBucket,bucket.Owner not equal to actionCtx.Caller 1917 { 1918 identityset.Address(1), 1919 "100000000000000000000", 1920 0, 1921 1000, 1922 0, 1923 big.NewInt(unit.Qev), 1924 10000, 1925 1, 1926 1, 1927 time.Now(), 1928 10000, 1929 identityset.Address(2), 1930 1, 1931 true, 1932 nil, 1933 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 1934 }, 1935 // fetchBucket,inMemCandidates.ContainsSelfStakingBucket is false 1936 { 1937 identityset.Address(1), 1938 "100000000000000000000", 1939 0, 1940 101, 1941 1, 1942 big.NewInt(unit.Qev), 1943 10000, 1944 1, 1945 1, 1946 time.Now(), 1947 10000, 1948 identityset.Address(2), 1949 1, 1950 true, 1951 nil, 1952 iotextypes.ReceiptStatus_ErrInvalidBucketType, 1953 }, 1954 { 1955 identityset.Address(2), 1956 "100000000000000000000", 1957 0, 1958 101, 1959 0, 1960 big.NewInt(unit.Qev), 1961 10000, 1962 2, 1963 1, 1964 time.Now(), 1965 10000, 1966 identityset.Address(1), 1967 1, 1968 false, 1969 nil, 1970 iotextypes.ReceiptStatus_Success, 1971 }, 1972 // test transfer to same owner 1973 { 1974 identityset.Address(2), 1975 "100000000000000000000", 1976 0, 1977 101, 1978 0, 1979 big.NewInt(unit.Qev), 1980 10000, 1981 2, 1982 genesis.Default.HawaiiBlockHeight, 1983 time.Now(), 1984 10000, 1985 identityset.Address(2), 1986 1, 1987 false, 1988 nil, 1989 iotextypes.ReceiptStatus_ErrInvalidBucketType, 1990 }, 1991 } 1992 1993 for _, test := range tests { 1994 sm, p, candi, candidate2 := initAll(t, ctrl) 1995 csr := newCandidateStateReader(sm) 1996 nonce := uint64(1) 1997 if test.caller.String() == candidate2.Owner.String() { 1998 nonce++ 1999 } 2000 ctx, createCost := initCreateStake(t, sm, candidate2.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate2, test.amount, false) 2001 if test.init { 2002 initCreateStake(t, sm, candi.Owner, test.initBalance, test.gasPrice, test.gasLimit, test.nonce, test.blkHeight, test.blkTimestamp, test.blkGasLimit, p, candi, test.amount, false) 2003 if test.caller.String() == candi.Owner.String() { 2004 nonce++ 2005 } 2006 } else { 2007 require.NoError(setupAccount(sm, identityset.Address(1), 1)) 2008 } 2009 2010 act, err := action.NewTransferStake(nonce, test.to.String(), test.index, nil, test.gasLimit, test.gasPrice) 2011 require.NoError(err) 2012 intrinsic, err := act.IntrinsicGas() 2013 require.NoError(err) 2014 2015 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2016 Caller: test.caller, 2017 GasPrice: test.gasPrice, 2018 IntrinsicGas: intrinsic, 2019 Nonce: nonce, 2020 }) 2021 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2022 BlockHeight: test.blkHeight, 2023 BlockTimeStamp: time.Now(), 2024 GasLimit: 10000000, 2025 }) 2026 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2027 r, err := p.Handle(ctx, act, sm) 2028 require.Equal(test.err, errors.Cause(err)) 2029 if r != nil { 2030 require.Equal(uint64(test.status), r.Status) 2031 } else { 2032 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 2033 } 2034 2035 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 2036 // test bucket index and bucket 2037 bucketIndices, _, err := csr.candBucketIndices(candidate2.Owner) 2038 require.NoError(err) 2039 require.Equal(1, len(*bucketIndices)) 2040 bucketIndices, _, err = csr.voterBucketIndices(test.to) 2041 require.NoError(err) 2042 require.Equal(1, len(*bucketIndices)) 2043 indices := *bucketIndices 2044 bucket, err := csr.getBucket(indices[0]) 2045 require.NoError(err) 2046 require.Equal(candidate2.Owner, bucket.Candidate) 2047 require.Equal(test.to.String(), bucket.Owner.String()) 2048 require.Equal(test.amount, bucket.StakedAmount.String()) 2049 2050 // test candidate 2051 candidate, _, err := csr.getCandidate(candi.Owner) 2052 require.NoError(err) 2053 require.Equal(test.afterTransfer, candidate.Votes.Uint64()) 2054 csm, err := NewCandidateStateManager(sm, false) 2055 require.NoError(err) 2056 candidate = csm.GetByOwner(candi.Owner) 2057 require.NotNil(candidate) 2058 require.LessOrEqual(test.afterTransfer, candidate.Votes.Uint64()) 2059 require.Equal(candi.Name, candidate.Name) 2060 require.Equal(candi.Operator, candidate.Operator) 2061 require.Equal(candi.Reward, candidate.Reward) 2062 require.Equal(candi.Owner, candidate.Owner) 2063 require.Equal(test.afterTransfer, candidate.Votes.Uint64()) 2064 require.LessOrEqual(test.afterTransfer, candidate.SelfStake.Uint64()) 2065 2066 // test staker's account 2067 caller, err := accountutil.LoadAccount(sm, test.caller) 2068 require.NoError(err) 2069 actCost, err := act.Cost() 2070 require.NoError(err) 2071 require.Equal(test.nonce+1, caller.PendingNonce()) 2072 total := big.NewInt(0) 2073 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost)) 2074 } 2075 } 2076 } 2077 2078 func TestProtocol_HandleConsignmentTransfer(t *testing.T) { 2079 require := require.New(t) 2080 ctrl := gomock.NewController(t) 2081 2082 tests := []struct { 2083 bucketOwner string 2084 blkHeight uint64 2085 to address.Address 2086 // consignment fields 2087 nilPayload bool 2088 consignType string 2089 reclaim string 2090 wrongSig bool 2091 sigIndex uint64 2092 sigNonce uint64 2093 status iotextypes.ReceiptStatus 2094 }{ 2095 // case I: p.hu.IsPreGreenland(blkCtx.BlockHeight) 2096 { 2097 identityset.PrivateKey(2).HexString(), 2098 1, 2099 identityset.Address(3), 2100 false, 2101 "Ethereum", 2102 _reclaim, 2103 false, 2104 0, 2105 1, 2106 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2107 }, 2108 // case II: len(act.Payload()) == 0 2109 { 2110 identityset.PrivateKey(2).HexString(), 2111 5553821, 2112 identityset.Address(3), 2113 true, 2114 "Ethereum", 2115 _reclaim, 2116 false, 2117 0, 2118 1, 2119 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2120 }, 2121 // case III: type is not Ethereum 2122 { 2123 identityset.PrivateKey(2).HexString(), 2124 5553821, 2125 identityset.Address(3), 2126 false, 2127 "xx", 2128 _reclaim, 2129 false, 2130 0, 2131 1, 2132 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2133 }, 2134 // case IV: msg.Reclaim != _reclaim 2135 { 2136 identityset.PrivateKey(2).HexString(), 2137 5553821, 2138 identityset.Address(3), 2139 false, 2140 "Ethereum", 2141 "wrong reclaim", 2142 false, 2143 0, 2144 1, 2145 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2146 }, 2147 // case V: RecoverPubkeyFromEccSig error 2148 { 2149 identityset.PrivateKey(2).HexString(), 2150 5553821, 2151 identityset.Address(3), 2152 false, 2153 "Ethereum", 2154 _reclaim, 2155 true, 2156 0, 2157 1, 2158 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2159 }, 2160 // case VI: transferor is not bucket.Owner 2161 { 2162 identityset.PrivateKey(31).HexString(), 2163 5553821, 2164 identityset.Address(1), 2165 false, 2166 "Ethereum", 2167 _reclaim, 2168 false, 2169 0, 2170 1, 2171 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2172 }, 2173 // case VII: transferee is not actCtx.Caller 2174 { 2175 identityset.PrivateKey(32).HexString(), 2176 5553821, 2177 identityset.Address(3), 2178 false, 2179 "Ethereum", 2180 _reclaim, 2181 false, 2182 0, 2183 1, 2184 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2185 }, 2186 // case VIII: signed asset id is not equal to bucket.Index 2187 { 2188 identityset.PrivateKey(32).HexString(), 2189 5553821, 2190 identityset.Address(1), 2191 false, 2192 "Ethereum", 2193 _reclaim, 2194 false, 2195 1, 2196 1, 2197 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2198 }, 2199 // case IX: transfereeNonce is not equal to actCtx.Nonce 2200 { 2201 identityset.PrivateKey(32).HexString(), 2202 5553821, 2203 identityset.Address(1), 2204 false, 2205 "Ethereum", 2206 _reclaim, 2207 false, 2208 0, 2209 2, 2210 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2211 }, 2212 // case X: success 2213 { 2214 identityset.PrivateKey(32).HexString(), 2215 6544441, 2216 identityset.Address(1), 2217 false, 2218 "Ethereum", 2219 _reclaim, 2220 false, 2221 0, 2222 1, 2223 iotextypes.ReceiptStatus_Success, 2224 }, 2225 } 2226 for _, test := range tests { 2227 sm, p, cand1, cand2 := initAll(t, ctrl) 2228 csr := newCandidateStateReader(sm) 2229 caller := identityset.Address(1) 2230 initBalance := int64(1000) 2231 require.NoError(setupAccount(sm, caller, initBalance)) 2232 stakeAmount := "100000000000000000000" 2233 gasPrice := big.NewInt(unit.Qev) 2234 gasLimit := uint64(10000) 2235 ctx, _ := initCreateStake(t, sm, identityset.Address(32), initBalance, gasPrice, gasLimit, 1, test.blkHeight, time.Now(), gasLimit, p, cand2, stakeAmount, false) 2236 initCreateStake(t, sm, identityset.Address(31), initBalance, gasPrice, gasLimit, 1, test.blkHeight, time.Now(), gasLimit, p, cand1, stakeAmount, false) 2237 2238 // transfer to test.to through consignment 2239 var consign []byte 2240 if !test.nilPayload { 2241 consign = newconsignment(require, test.sigIndex, test.sigNonce, test.bucketOwner, test.to.String(), test.consignType, test.reclaim, test.wrongSig) 2242 } 2243 2244 act, err := action.NewTransferStake(1, caller.String(), 0, consign, gasLimit, gasPrice) 2245 require.NoError(err) 2246 intrinsic, err := act.IntrinsicGas() 2247 require.NoError(err) 2248 2249 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2250 Caller: caller, 2251 GasPrice: gasPrice, 2252 IntrinsicGas: intrinsic, 2253 Nonce: 1, 2254 }) 2255 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2256 BlockHeight: test.blkHeight, 2257 BlockTimeStamp: time.Now(), 2258 GasLimit: gasLimit, 2259 }) 2260 r, err := p.Handle(ctx, act, sm) 2261 require.NoError(err) 2262 if r != nil { 2263 require.Equal(uint64(test.status), r.Status) 2264 } else { 2265 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 2266 } 2267 2268 if test.status == iotextypes.ReceiptStatus_Success { 2269 // test bucket index and bucket 2270 bucketIndices, _, err := csr.candBucketIndices(cand2.Owner) 2271 require.NoError(err) 2272 require.Equal(1, len(*bucketIndices)) 2273 bucketIndices, _, err = csr.voterBucketIndices(test.to) 2274 require.NoError(err) 2275 require.Equal(1, len(*bucketIndices)) 2276 indices := *bucketIndices 2277 bucket, err := csr.getBucket(indices[0]) 2278 require.NoError(err) 2279 require.Equal(cand2.Owner, bucket.Candidate) 2280 require.Equal(test.to.String(), bucket.Owner.String()) 2281 require.Equal(stakeAmount, bucket.StakedAmount.String()) 2282 2283 // test candidate 2284 candidate, _, err := csr.getCandidate(cand1.Owner) 2285 require.NoError(err) 2286 require.LessOrEqual(uint64(0), candidate.Votes.Uint64()) 2287 csm, err := NewCandidateStateManager(sm, false) 2288 require.NoError(err) 2289 candidate = csm.GetByOwner(cand1.Owner) 2290 require.NotNil(candidate) 2291 require.LessOrEqual(uint64(0), candidate.Votes.Uint64()) 2292 require.Equal(cand1.Name, candidate.Name) 2293 require.Equal(cand1.Operator, candidate.Operator) 2294 require.Equal(cand1.Reward, candidate.Reward) 2295 require.Equal(cand1.Owner, candidate.Owner) 2296 require.LessOrEqual(uint64(0), candidate.Votes.Uint64()) 2297 require.LessOrEqual(uint64(0), candidate.SelfStake.Uint64()) 2298 2299 // test staker's account 2300 caller, err := accountutil.LoadAccount(sm, caller) 2301 require.NoError(err) 2302 actCost, err := act.Cost() 2303 require.NoError(err) 2304 require.Equal(uint64(2), caller.PendingNonce()) 2305 total := big.NewInt(0) 2306 require.Equal(unit.ConvertIotxToRau(initBalance), total.Add(total, caller.Balance).Add(total, actCost)) 2307 } 2308 } 2309 } 2310 2311 func TestProtocol_HandleRestake(t *testing.T) { 2312 require := require.New(t) 2313 ctrl := gomock.NewController(t) 2314 callerAddr := identityset.Address(2) 2315 2316 tests := []struct { 2317 // creat stake fields 2318 caller address.Address 2319 amount string 2320 afterRestake string 2321 initBalance int64 2322 selfstaking bool 2323 // action fields 2324 index uint64 2325 gasPrice *big.Int 2326 gasLimit uint64 2327 nonce uint64 2328 // block context 2329 blkHeight uint64 2330 blkTimestamp time.Time 2331 blkGasLimit uint64 2332 // restake fields 2333 duration uint32 2334 autoStake bool 2335 // clear flag for inMemCandidates 2336 clear bool 2337 // need new p 2338 newAccount bool 2339 // expected result 2340 err error 2341 status iotextypes.ReceiptStatus 2342 }{ 2343 // fetchCaller ReceiptStatus_ErrNotEnoughBalance 2344 { 2345 callerAddr, 2346 "100990000000000000000", 2347 "0", 2348 101, 2349 false, 2350 0, 2351 big.NewInt(unit.Qev), 2352 10000, 2353 1, 2354 1, 2355 time.Now(), 2356 10000, 2357 1, 2358 true, 2359 false, 2360 false, 2361 nil, 2362 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 2363 }, 2364 // fetchBucket, bucket.Owner is not equal to actionCtx.Caller 2365 { 2366 identityset.Address(12), 2367 "100000000000000000000", 2368 "0", 2369 101, 2370 false, 2371 0, 2372 big.NewInt(unit.Qev), 2373 10000, 2374 1, 2375 1, 2376 time.Now(), 2377 10000, 2378 1, 2379 true, 2380 false, 2381 true, 2382 nil, 2383 iotextypes.ReceiptStatus_ErrUnauthorizedOperator, 2384 }, 2385 // updateBucket getbucket ErrStateNotExist 2386 { 2387 identityset.Address(33), 2388 "100000000000000000000", 2389 "0", 2390 101, 2391 false, 2392 1, 2393 big.NewInt(unit.Qev), 2394 10000, 2395 1, 2396 1, 2397 time.Now(), 2398 10000, 2399 1, 2400 true, 2401 false, 2402 true, 2403 nil, 2404 iotextypes.ReceiptStatus_ErrInvalidBucketIndex, 2405 }, 2406 // for inMemCandidates.GetByOwner,ErrInvalidOwner 2407 { 2408 callerAddr, 2409 "100000000000000000000", 2410 "0", 2411 101, 2412 false, 2413 0, 2414 big.NewInt(unit.Qev), 2415 10000, 2416 1, 2417 1, 2418 time.Now(), 2419 10000, 2420 1, 2421 true, 2422 true, 2423 false, 2424 nil, 2425 iotextypes.ReceiptStatus_ErrCandidateNotExist, 2426 }, 2427 // autoStake = true, set up duration 2428 { 2429 callerAddr, 2430 "100000000000000000000", 2431 "103801784016923925869", 2432 101, 2433 false, 2434 0, 2435 big.NewInt(unit.Qev), 2436 10000, 2437 1, 2438 1, 2439 time.Now(), 2440 10000, 2441 0, 2442 true, 2443 false, 2444 false, 2445 nil, 2446 iotextypes.ReceiptStatus_ErrInvalidBucketType, 2447 }, 2448 // autoStake = false, set up duration 2449 { 2450 callerAddr, 2451 "100000000000000000000", 2452 "103801784016923925869", 2453 101, 2454 false, 2455 0, 2456 big.NewInt(unit.Qev), 2457 10000, 2458 1, 2459 1, 2460 time.Now(), 2461 10000, 2462 0, 2463 false, 2464 false, 2465 false, 2466 nil, 2467 iotextypes.ReceiptStatus_ErrReduceDurationBeforeMaturity, 2468 }, 2469 // candidate.SubVote,ErrInvalidAmount cannot happen,because previous vote with no error 2470 // ReceiptStatus_Success 2471 { 2472 callerAddr, 2473 "100000000000000000000", 2474 "103801784016923925869", 2475 101, 2476 false, 2477 0, 2478 big.NewInt(unit.Qev), 2479 10000, 2480 1, 2481 1, 2482 time.Now(), 2483 10000, 2484 1, 2485 true, 2486 false, 2487 false, 2488 nil, 2489 iotextypes.ReceiptStatus_Success, 2490 }, 2491 } 2492 2493 for _, test := range tests { 2494 sm, p, candidate, candidate2 := initAll(t, ctrl) 2495 csr := newCandidateStateReader(sm) 2496 ctx, createCost := initCreateStake(t, sm, candidate2.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, test.nonce, 1, time.Now(), 10000, p, candidate2, test.amount, test.autoStake) 2497 2498 if test.newAccount { 2499 require.NoError(setupAccount(sm, test.caller, test.initBalance)) 2500 } else { 2501 candidate = candidate2 2502 } 2503 nonce := test.nonce 2504 if test.caller.String() == candidate2.Owner.String() { 2505 nonce++ 2506 } 2507 2508 act, err := action.NewRestake(nonce, test.index, test.duration, test.autoStake, nil, test.gasLimit, test.gasPrice) 2509 require.NoError(err) 2510 intrinsic, err := act.IntrinsicGas() 2511 require.NoError(err) 2512 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2513 Caller: test.caller, 2514 GasPrice: test.gasPrice, 2515 IntrinsicGas: intrinsic, 2516 Nonce: nonce, 2517 }) 2518 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2519 BlockHeight: 1, 2520 BlockTimeStamp: time.Now(), 2521 GasLimit: 10000000, 2522 }) 2523 var r *action.Receipt 2524 if test.clear { 2525 csm, err := NewCandidateStateManager(sm, false) 2526 require.NoError(err) 2527 sc, ok := csm.(*candSM) 2528 require.True(ok) 2529 sc.candCenter.deleteForTestOnly(test.caller) 2530 require.False(csm.ContainsOwner(test.caller)) 2531 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2532 r, err = p.handle(ctx, act, csm) 2533 require.Equal(test.err, errors.Cause(err)) 2534 } else { 2535 r, err = p.Handle(ctx, act, sm) 2536 require.Equal(test.err, errors.Cause(err)) 2537 } 2538 if r != nil { 2539 require.Equal(uint64(test.status), r.Status) 2540 } else { 2541 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 2542 } 2543 2544 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 2545 // test bucket index and bucket 2546 bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) 2547 require.NoError(err) 2548 require.Equal(1, len(*bucketIndices)) 2549 bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) 2550 require.NoError(err) 2551 require.Equal(1, len(*bucketIndices)) 2552 indices := *bucketIndices 2553 bucket, err := csr.getBucket(indices[0]) 2554 require.NoError(err) 2555 require.Equal(candidate.Owner.String(), bucket.Candidate.String()) 2556 require.Equal(test.caller.String(), bucket.Owner.String()) 2557 require.Equal(test.amount, bucket.StakedAmount.String()) 2558 2559 // test candidate 2560 candidate, _, err = csr.getCandidate(candidate.Owner) 2561 require.NoError(err) 2562 require.Equal(test.afterRestake, candidate.Votes.String()) 2563 csm, err := NewCandidateStateManager(sm, false) 2564 require.NoError(err) 2565 candidate = csm.GetByOwner(candidate.Owner) 2566 require.NotNil(candidate) 2567 require.Equal(test.afterRestake, candidate.Votes.String()) 2568 2569 // test staker's account 2570 caller, err := accountutil.LoadAccount(sm, test.caller) 2571 require.NoError(err) 2572 actCost, err := act.Cost() 2573 require.NoError(err) 2574 require.Equal(test.nonce+2, caller.PendingNonce()) 2575 total := big.NewInt(0) 2576 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost)) 2577 } 2578 } 2579 } 2580 2581 func TestProtocol_HandleDepositToStake(t *testing.T) { 2582 require := require.New(t) 2583 ctrl := gomock.NewController(t) 2584 tests := []struct { 2585 // creat stake fields 2586 caller address.Address 2587 amount string 2588 afterDeposit string 2589 initBalance int64 2590 selfstaking bool 2591 // action fields 2592 index uint64 2593 gasPrice *big.Int 2594 gasLimit uint64 2595 nonce uint64 2596 // block context 2597 blkHeight uint64 2598 blkTimestamp time.Time 2599 blkGasLimit uint64 2600 autoStake bool 2601 // clear flag for inMemCandidates 2602 clear bool 2603 // need new p 2604 newAccount bool 2605 // expected result 2606 err error 2607 status iotextypes.ReceiptStatus 2608 }{ 2609 // fetchCaller ErrNotEnoughBalance 2610 { 2611 identityset.Address(1), 2612 "100000000000000000000", 2613 "0", 2614 10, 2615 false, 2616 0, 2617 big.NewInt(unit.Qev), 2618 10000, 2619 2, 2620 1, 2621 time.Now(), 2622 10000, 2623 false, 2624 false, 2625 false, 2626 nil, 2627 iotextypes.ReceiptStatus_ErrNotEnoughBalance, 2628 }, 2629 // fetchBucket ReceiptStatus_ErrInvalidBucketIndex 2630 { 2631 identityset.Address(12), 2632 "100000000000000000000", 2633 "0", 2634 101, 2635 false, 2636 1, 2637 big.NewInt(unit.Qev), 2638 10000, 2639 1, 2640 1, 2641 time.Now(), 2642 10000, 2643 false, 2644 false, 2645 true, 2646 nil, 2647 iotextypes.ReceiptStatus_ErrInvalidBucketIndex, 2648 }, 2649 // fetchBucket ReceiptStatus_ErrInvalidBucketType 2650 { 2651 identityset.Address(33), 2652 "100000000000000000000", 2653 "0", 2654 101, 2655 false, 2656 0, 2657 big.NewInt(unit.Qev), 2658 10000, 2659 1, 2660 1, 2661 time.Now(), 2662 10000, 2663 false, 2664 true, 2665 true, 2666 nil, 2667 iotextypes.ReceiptStatus_ErrInvalidBucketType, 2668 }, 2669 // for inMemCandidates.GetByOwner,ErrInvalidOwner 2670 { 2671 identityset.Address(1), 2672 "100000000000000000000", 2673 "0", 2674 201, 2675 false, 2676 0, 2677 big.NewInt(unit.Qev), 2678 10000, 2679 2, 2680 1, 2681 time.Now(), 2682 10000, 2683 true, 2684 true, 2685 false, 2686 nil, 2687 iotextypes.ReceiptStatus_ErrCandidateNotExist, 2688 }, 2689 // ReceiptStatus_Success 2690 { 2691 identityset.Address(1), 2692 "100000000000000000000", 2693 "207603568033847851737", 2694 201, 2695 false, 2696 0, 2697 big.NewInt(unit.Qev), 2698 10000, 2699 2, 2700 1, 2701 time.Now(), 2702 10000, 2703 true, 2704 false, 2705 false, 2706 nil, 2707 iotextypes.ReceiptStatus_Success, 2708 }, 2709 } 2710 2711 for _, test := range tests { 2712 sm, p, candidate, _ := initAll(t, ctrl) 2713 csr := newCandidateStateReader(sm) 2714 ctx, createCost := initCreateStake(t, sm, candidate.Owner, test.initBalance, big.NewInt(unit.Qev), 10000, 1, 1, time.Now(), 10000, p, candidate, test.amount, test.autoStake) 2715 2716 if test.newAccount { 2717 require.NoError(setupAccount(sm, test.caller, test.initBalance)) 2718 } 2719 2720 act, err := action.NewDepositToStake(test.nonce, test.index, test.amount, nil, test.gasLimit, test.gasPrice) 2721 require.NoError(err) 2722 intrinsic, err := act.IntrinsicGas() 2723 require.NoError(err) 2724 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2725 Caller: test.caller, 2726 GasPrice: test.gasPrice, 2727 IntrinsicGas: intrinsic, 2728 Nonce: test.nonce, 2729 }) 2730 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2731 BlockHeight: 1, 2732 BlockTimeStamp: time.Now(), 2733 GasLimit: 10000000, 2734 }) 2735 var r *action.Receipt 2736 if test.clear { 2737 csm, err := NewCandidateStateManager(sm, false) 2738 require.NoError(err) 2739 sc, ok := csm.(*candSM) 2740 require.True(ok) 2741 sc.candCenter.deleteForTestOnly(test.caller) 2742 require.False(csm.ContainsOwner(test.caller)) 2743 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2744 r, err = p.handle(ctx, act, csm) 2745 require.Equal(test.err, errors.Cause(err)) 2746 } else { 2747 r, err = p.Handle(ctx, act, sm) 2748 require.Equal(test.err, errors.Cause(err)) 2749 } 2750 if r != nil { 2751 require.Equal(uint64(test.status), r.Status) 2752 } else { 2753 require.Equal(test.status, iotextypes.ReceiptStatus_Failure) 2754 } 2755 2756 if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { 2757 // check the special deposit bucket log 2758 tLogs := r.TransactionLogs() 2759 require.Equal(1, len(tLogs)) 2760 dLog := tLogs[0] 2761 require.Equal(test.caller.String(), dLog.Sender) 2762 require.Equal(address.StakingBucketPoolAddr, dLog.Recipient) 2763 require.Equal(test.amount, dLog.Amount.String()) 2764 2765 // test bucket index and bucket 2766 bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) 2767 require.NoError(err) 2768 require.Equal(1, len(*bucketIndices)) 2769 bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) 2770 require.NoError(err) 2771 require.Equal(1, len(*bucketIndices)) 2772 indices := *bucketIndices 2773 2774 bucket, err := csr.getBucket(indices[0]) 2775 require.NoError(err) 2776 require.Equal(candidate.Owner.String(), bucket.Candidate.String()) 2777 require.Equal(test.caller.String(), bucket.Owner.String()) 2778 amount, _ := new(big.Int).SetString(test.amount, 10) 2779 require.Zero(new(big.Int).Mul(amount, big.NewInt(2)).Cmp(bucket.StakedAmount)) 2780 2781 // test candidate 2782 candidate, _, err = csr.getCandidate(candidate.Owner) 2783 require.NoError(err) 2784 require.Equal(test.afterDeposit, candidate.Votes.String()) 2785 csm, err := NewCandidateStateManager(sm, false) 2786 require.NoError(err) 2787 candidate = csm.GetByOwner(candidate.Owner) 2788 require.NotNil(candidate) 2789 require.Equal(test.afterDeposit, candidate.Votes.String()) 2790 2791 // test staker's account 2792 caller, err := accountutil.LoadAccount(sm, test.caller) 2793 require.NoError(err) 2794 actCost, err := act.Cost() 2795 require.NoError(err) 2796 require.Equal(test.nonce+1, caller.PendingNonce()) 2797 total := big.NewInt(0) 2798 require.Equal(unit.ConvertIotxToRau(test.initBalance), total.Add(total, caller.Balance).Add(total, actCost).Add(total, createCost)) 2799 } 2800 } 2801 } 2802 2803 func TestProtocol_FetchBucketAndValidate(t *testing.T) { 2804 require := require.New(t) 2805 ctrl := gomock.NewController(t) 2806 sm, p, _, _ := initAll(t, ctrl) 2807 2808 t.Run("bucket not exist", func(t *testing.T) { 2809 csm, err := NewCandidateStateManager(sm, false) 2810 require.NoError(err) 2811 patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { 2812 return nil, state.ErrStateNotExist 2813 }) 2814 defer patches.Reset() 2815 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, true) 2816 require.ErrorContains(err, "failed to fetch bucket") 2817 }) 2818 t.Run("validate owner", func(t *testing.T) { 2819 csm, err := NewCandidateStateManager(sm, false) 2820 require.NoError(err) 2821 patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { 2822 return &VoteBucket{ 2823 Owner: identityset.Address(1), 2824 }, nil 2825 }) 2826 defer patches.Reset() 2827 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(2), 1, true, true) 2828 require.ErrorContains(err, "bucket owner does not match") 2829 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, true) 2830 require.NoError(err) 2831 }) 2832 t.Run("validate selfstake", func(t *testing.T) { 2833 csm, err := NewCandidateStateManager(sm, false) 2834 require.NoError(err) 2835 patches := gomonkey.NewPatches() 2836 defer patches.Reset() 2837 patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { 2838 return &VoteBucket{ 2839 Owner: identityset.Address(1), 2840 }, nil 2841 }) 2842 isSelfStake := true 2843 isSelfStakeErr := error(nil) 2844 patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) { 2845 return isSelfStake, isSelfStakeErr 2846 }) 2847 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false) 2848 require.ErrorContains(err, "self staking bucket cannot be processed") 2849 isSelfStake = false 2850 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false) 2851 require.NoError(err) 2852 isSelfStakeErr = errors.New("unknown error") 2853 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, false, false) 2854 require.ErrorContains(err, "unknown error") 2855 }) 2856 t.Run("validate owner and selfstake", func(t *testing.T) { 2857 csm, err := NewCandidateStateManager(sm, false) 2858 require.NoError(err) 2859 patches := gomonkey.NewPatches() 2860 patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { 2861 return &VoteBucket{ 2862 Owner: identityset.Address(1), 2863 }, nil 2864 }) 2865 patches.ApplyFunc(isSelfStakeBucket, func(featureCtx protocol.FeatureCtx, csm CandidiateStateCommon, bucket *VoteBucket) (bool, error) { 2866 return false, nil 2867 }) 2868 defer patches.Reset() 2869 2870 _, err = p.fetchBucketAndValidate(protocol.FeatureCtx{}, csm, identityset.Address(1), 1, true, false) 2871 require.NoError(err) 2872 }) 2873 } 2874 2875 func TestChangeCandidate(t *testing.T) { 2876 r := require.New(t) 2877 ctrl := gomock.NewController(t) 2878 t.Run("Self-Staked as owned", func(t *testing.T) { 2879 bucketCfgs := []*bucketConfig{ 2880 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0}, 2881 } 2882 candCfgs := []*candidateConfig{ 2883 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 2884 } 2885 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 2886 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 2887 // csm, err := NewCandidateStateManager(sm, false) 2888 nonce := uint64(1) 2889 act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 2890 r.NoError(err) 2891 intrinsic, err := act.IntrinsicGas() 2892 r.NoError(err) 2893 ctx := context.Background() 2894 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 2895 g.TsunamiBlockHeight = 0 2896 ctx = genesis.WithGenesisContext(ctx, g) 2897 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2898 Caller: identityset.Address(1), 2899 GasPrice: big.NewInt(unit.Qev), 2900 IntrinsicGas: intrinsic, 2901 Nonce: nonce, 2902 }) 2903 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2904 BlockHeight: 2, 2905 BlockTimeStamp: time.Now(), 2906 GasLimit: 1000000, 2907 }) 2908 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2909 recipt, err := p.Handle(ctx, act, sm) 2910 r.NoError(err) 2911 r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status) 2912 }) 2913 t.Run("Self-Staked with endorsement", func(t *testing.T) { 2914 bucketCfgs := []*bucketConfig{ 2915 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, true, nil, 0}, 2916 } 2917 candCfgs := []*candidateConfig{ 2918 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 2919 } 2920 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 2921 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 2922 esm := NewEndorsementStateManager(sm) 2923 err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: endorsementNotExpireHeight}) 2924 r.NoError(err) 2925 nonce := uint64(1) 2926 act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 2927 r.NoError(err) 2928 intrinsic, err := act.IntrinsicGas() 2929 r.NoError(err) 2930 ctx := context.Background() 2931 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 2932 g.TsunamiBlockHeight = 0 2933 ctx = genesis.WithGenesisContext(ctx, g) 2934 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2935 Caller: identityset.Address(1), 2936 GasPrice: big.NewInt(unit.Qev), 2937 IntrinsicGas: intrinsic, 2938 Nonce: nonce, 2939 }) 2940 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2941 BlockHeight: 2, 2942 BlockTimeStamp: time.Now(), 2943 GasLimit: 1000000, 2944 }) 2945 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2946 recipt, err := p.Handle(ctx, act, sm) 2947 r.NoError(err) 2948 r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status) 2949 }) 2950 t.Run("Endorsement is withdrawing", func(t *testing.T) { 2951 bucketCfgs := []*bucketConfig{ 2952 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 0}, 2953 } 2954 candCfgs := []*candidateConfig{ 2955 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 2956 } 2957 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 2958 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 2959 esm := NewEndorsementStateManager(sm) 2960 err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: 10}) 2961 r.NoError(err) 2962 nonce := uint64(1) 2963 act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 2964 r.NoError(err) 2965 intrinsic, err := act.IntrinsicGas() 2966 r.NoError(err) 2967 ctx := context.Background() 2968 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 2969 g.TsunamiBlockHeight = 0 2970 ctx = genesis.WithGenesisContext(ctx, g) 2971 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 2972 Caller: identityset.Address(1), 2973 GasPrice: big.NewInt(unit.Qev), 2974 IntrinsicGas: intrinsic, 2975 Nonce: nonce, 2976 }) 2977 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 2978 BlockHeight: 2, 2979 BlockTimeStamp: time.Now(), 2980 GasLimit: 1000000, 2981 }) 2982 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 2983 recipt, err := p.Handle(ctx, act, sm) 2984 r.NoError(err) 2985 r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status) 2986 }) 2987 t.Run("Endorsement is expired", func(t *testing.T) { 2988 bucketCfgs := []*bucketConfig{ 2989 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 1}, 2990 } 2991 candCfgs := []*candidateConfig{ 2992 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 2993 } 2994 sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 2) 2995 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 2996 nonce := uint64(1) 2997 act, err := action.NewChangeCandidate(nonce, "test1", buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 2998 r.NoError(err) 2999 intrinsic, err := act.IntrinsicGas() 3000 r.NoError(err) 3001 ctx := context.Background() 3002 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 3003 g.TsunamiBlockHeight = 0 3004 ctx = genesis.WithGenesisContext(ctx, g) 3005 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 3006 Caller: identityset.Address(1), 3007 GasPrice: big.NewInt(unit.Qev), 3008 IntrinsicGas: intrinsic, 3009 Nonce: nonce, 3010 }) 3011 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3012 BlockHeight: 2, 3013 BlockTimeStamp: time.Now(), 3014 GasLimit: 1000000, 3015 }) 3016 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3017 recipt, err := p.Handle(ctx, act, sm) 3018 r.NoError(err) 3019 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 3020 }) 3021 } 3022 3023 func TestUnstake(t *testing.T) { 3024 r := require.New(t) 3025 ctrl := gomock.NewController(t) 3026 t.Run("Self-Staked as owned", func(t *testing.T) { 3027 bucketCfgs := []*bucketConfig{ 3028 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, true, nil, 0}, 3029 } 3030 candCfgs := []*candidateConfig{ 3031 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 3032 } 3033 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 3034 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 3035 nonce := uint64(1) 3036 act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 3037 r.NoError(err) 3038 intrinsic, err := act.IntrinsicGas() 3039 r.NoError(err) 3040 ctx := context.Background() 3041 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 3042 g.TsunamiBlockHeight = 0 3043 ctx = genesis.WithGenesisContext(ctx, g) 3044 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 3045 Caller: identityset.Address(1), 3046 GasPrice: big.NewInt(unit.Qev), 3047 IntrinsicGas: intrinsic, 3048 Nonce: nonce, 3049 }) 3050 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3051 BlockHeight: 2, 3052 BlockTimeStamp: time.Now(), 3053 GasLimit: 1000000, 3054 }) 3055 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3056 recipt, err := p.Handle(ctx, act, sm) 3057 r.NoError(err) 3058 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 3059 }) 3060 t.Run("Self-Staked with endorsement", func(t *testing.T) { 3061 bucketCfgs := []*bucketConfig{ 3062 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, true, nil, 0}, 3063 } 3064 candCfgs := []*candidateConfig{ 3065 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 3066 } 3067 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 3068 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 3069 esm := NewEndorsementStateManager(sm) 3070 err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: endorsementNotExpireHeight}) 3071 r.NoError(err) 3072 nonce := uint64(1) 3073 act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 3074 r.NoError(err) 3075 intrinsic, err := act.IntrinsicGas() 3076 r.NoError(err) 3077 ctx := context.Background() 3078 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 3079 g.TsunamiBlockHeight = 0 3080 ctx = genesis.WithGenesisContext(ctx, g) 3081 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 3082 Caller: identityset.Address(1), 3083 GasPrice: big.NewInt(unit.Qev), 3084 IntrinsicGas: intrinsic, 3085 Nonce: nonce, 3086 }) 3087 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3088 BlockHeight: 2, 3089 BlockTimeStamp: time.Now(), 3090 GasLimit: 1000000, 3091 }) 3092 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3093 recipt, err := p.Handle(ctx, act, sm) 3094 r.NoError(err) 3095 r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status) 3096 }) 3097 t.Run("Endorsement is withdrawing", func(t *testing.T) { 3098 bucketCfgs := []*bucketConfig{ 3099 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, true, false, nil, 0}, 3100 } 3101 candCfgs := []*candidateConfig{ 3102 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 3103 } 3104 sm, p, buckets, _ := initTestState(t, ctrl, bucketCfgs, candCfgs) 3105 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 3106 esm := NewEndorsementStateManager(sm) 3107 err := esm.Put(buckets[0].Index, &Endorsement{ExpireHeight: 10}) 3108 r.NoError(err) 3109 nonce := uint64(1) 3110 act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 3111 r.NoError(err) 3112 intrinsic, err := act.IntrinsicGas() 3113 r.NoError(err) 3114 ctx := context.Background() 3115 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 3116 g.TsunamiBlockHeight = 0 3117 ctx = genesis.WithGenesisContext(ctx, g) 3118 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 3119 Caller: identityset.Address(1), 3120 GasPrice: big.NewInt(unit.Qev), 3121 IntrinsicGas: intrinsic, 3122 Nonce: nonce, 3123 }) 3124 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3125 BlockHeight: 2, 3126 BlockTimeStamp: time.Now(), 3127 GasLimit: 1000000, 3128 }) 3129 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3130 recipt, err := p.Handle(ctx, act, sm) 3131 r.NoError(err) 3132 r.EqualValues(iotextypes.ReceiptStatus_ErrInvalidBucketType, recipt.Status) 3133 }) 3134 t.Run("Endorsement is expired", func(t *testing.T) { 3135 bucketCfgs := []*bucketConfig{ 3136 {identityset.Address(1), identityset.Address(1), "1200000000000000000000000", 100, false, false, nil, 1}, 3137 } 3138 candCfgs := []*candidateConfig{ 3139 {identityset.Address(1), identityset.Address(11), identityset.Address(21), "test1"}, 3140 } 3141 sm, p, buckets, _ := initTestStateWithHeight(t, ctrl, bucketCfgs, candCfgs, 1) 3142 r.NoError(setupAccount(sm, identityset.Address(1), 10000)) 3143 nonce := uint64(1) 3144 act, err := action.NewUnstake(nonce, buckets[0].Index, nil, 10000, big.NewInt(unit.Qev)) 3145 r.NoError(err) 3146 intrinsic, err := act.IntrinsicGas() 3147 r.NoError(err) 3148 ctx := context.Background() 3149 g := deepcopy.Copy(genesis.Default).(genesis.Genesis) 3150 g.TsunamiBlockHeight = 0 3151 ctx = genesis.WithGenesisContext(ctx, g) 3152 ctx = protocol.WithActionCtx(ctx, protocol.ActionCtx{ 3153 Caller: identityset.Address(1), 3154 GasPrice: big.NewInt(unit.Qev), 3155 IntrinsicGas: intrinsic, 3156 Nonce: nonce, 3157 }) 3158 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3159 BlockHeight: 2, 3160 BlockTimeStamp: time.Now(), 3161 GasLimit: 1000000, 3162 }) 3163 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3164 recipt, err := p.Handle(ctx, act, sm) 3165 r.NoError(err) 3166 r.EqualValues(iotextypes.ReceiptStatus_Success, recipt.Status) 3167 }) 3168 } 3169 3170 func initCreateStake(t *testing.T, sm protocol.StateManager, callerAddr address.Address, initBalance int64, gasPrice *big.Int, gasLimit uint64, nonce uint64, blkHeight uint64, blkTimestamp time.Time, blkGasLimit uint64, p *Protocol, candidate *Candidate, amount string, autoStake bool) (context.Context, *big.Int) { 3171 require := require.New(t) 3172 require.NoError(setupAccount(sm, callerAddr, initBalance)) 3173 a, err := action.NewCreateStake(nonce, candidate.Name, amount, 1, autoStake, 3174 nil, gasLimit, gasPrice) 3175 require.NoError(err) 3176 intrinsic, err := a.IntrinsicGas() 3177 require.NoError(err) 3178 ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{ 3179 Caller: callerAddr, 3180 GasPrice: gasPrice, 3181 IntrinsicGas: intrinsic, 3182 Nonce: nonce, 3183 }) 3184 ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ 3185 BlockHeight: blkHeight, 3186 BlockTimeStamp: blkTimestamp, 3187 GasLimit: blkGasLimit, 3188 }) 3189 ctx = genesis.WithGenesisContext(ctx, genesis.Default) 3190 ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) 3191 v, err := p.Start(ctx, sm) 3192 require.NoError(err) 3193 cc, ok := v.(*ViewData) 3194 require.True(ok) 3195 require.NoError(sm.WriteView(_protocolID, cc)) 3196 _, err = p.Handle(ctx, a, sm) 3197 require.NoError(err) 3198 cost, err := a.Cost() 3199 require.NoError(err) 3200 return ctx, cost 3201 } 3202 3203 func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Protocol, *Candidate, *Candidate) { 3204 require := require.New(t) 3205 sm := testdb.NewMockStateManager(ctrl) 3206 csm := newCandidateStateManager(sm) 3207 _, err := sm.PutState( 3208 &totalBucketCount{count: 0}, 3209 protocol.NamespaceOption(_stakingNameSpace), 3210 protocol.KeyOption(TotalBucketKey), 3211 ) 3212 require.NoError(err) 3213 3214 // create protocol 3215 p, err := NewProtocol(depositGas, &BuilderConfig{ 3216 Staking: genesis.Default.Staking, 3217 PersistStakingPatchBlock: math.MaxUint64, 3218 }, nil, nil, genesis.Default.GreenlandBlockHeight) 3219 require.NoError(err) 3220 3221 // set up candidate 3222 candidate := testCandidates[0].d.Clone() 3223 candidate.Votes = big.NewInt(0) 3224 require.NoError(csm.putCandidate(candidate)) 3225 candidate2 := testCandidates[1].d.Clone() 3226 candidate2.Votes = big.NewInt(0) 3227 require.NoError(csm.putCandidate(candidate2)) 3228 ctx := genesis.WithGenesisContext(context.Background(), genesis.Default) 3229 ctx = protocol.WithFeatureWithHeightCtx(ctx) 3230 v, err := p.Start(ctx, sm) 3231 require.NoError(err) 3232 cc, ok := v.(*ViewData) 3233 require.True(ok) 3234 require.NoError(sm.WriteView(_protocolID, cc)) 3235 return sm, p, candidate, candidate2 3236 } 3237 3238 func setupAccount(sm protocol.StateManager, addr address.Address, balance int64) error { 3239 if balance < 0 { 3240 return errors.New("balance cannot be negative") 3241 } 3242 account, err := accountutil.LoadOrCreateAccount(sm, addr, state.LegacyNonceAccountTypeOption()) 3243 if err != nil { 3244 return err 3245 } 3246 if err := account.SubBalance(account.Balance); err != nil { 3247 return err 3248 } 3249 if err := account.AddBalance(unit.ConvertIotxToRau(balance)); err != nil { 3250 return err 3251 } 3252 return accountutil.StoreAccount(sm, addr, account) 3253 } 3254 3255 func depositGas(ctx context.Context, sm protocol.StateManager, gasFee *big.Int) (*action.TransactionLog, error) { 3256 actionCtx := protocol.MustGetActionCtx(ctx) 3257 // Subtract balance from caller 3258 acc, err := accountutil.LoadAccount(sm, actionCtx.Caller) 3259 if err != nil { 3260 return nil, err 3261 } 3262 // TODO: replace with SubBalance, and then change `Balance` to a function 3263 acc.Balance = big.NewInt(0).Sub(acc.Balance, gasFee) 3264 return nil, accountutil.StoreAccount(sm, actionCtx.Caller, acc) 3265 } 3266 3267 func newconsignment(r *require.Assertions, bucketIdx, nonce uint64, senderPrivate, recipient, consignTpye, reclaim string, wrongSig bool) []byte { 3268 msg := action.ConsignMsgEther{ 3269 BucketIdx: bucketIdx, 3270 Nonce: nonce, 3271 Recipient: recipient, 3272 Reclaim: reclaim, 3273 } 3274 b, err := json.Marshal(msg) 3275 r.NoError(err) 3276 h, err := action.MsgHash(consignTpye, b) 3277 if err != nil { 3278 h = []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} 3279 } 3280 sk, err := crypto.HexStringToPrivateKey(senderPrivate) 3281 r.NoError(err) 3282 sig, err := sk.Sign(h) 3283 r.NoError(err) 3284 c := &action.ConsignJSON{ 3285 Type: consignTpye, 3286 Msg: string(b), 3287 Sig: hex.EncodeToString(sig), 3288 } 3289 if wrongSig { 3290 c.Sig = "123456" 3291 } 3292 b, err = json.Marshal(c) 3293 r.NoError(err) 3294 return b 3295 }