github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/handlers.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  	"math/big"
    11  	"time"
    12  
    13  	"github.com/iotexproject/go-pkgs/hash"
    14  	"github.com/iotexproject/iotex-address/address"
    15  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/iotexproject/iotex-core/action"
    19  	"github.com/iotexproject/iotex-core/action/protocol"
    20  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    21  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    22  	"github.com/iotexproject/iotex-core/state"
    23  )
    24  
    25  // constants
    26  const (
    27  	HandleCreateStake       = "createStake"
    28  	HandleUnstake           = "unstake"
    29  	HandleWithdrawStake     = "withdrawStake"
    30  	HandleChangeCandidate   = "changeCandidate"
    31  	HandleTransferStake     = "transferStake"
    32  	HandleDepositToStake    = "depositToStake"
    33  	HandleRestake           = "restake"
    34  	HandleCandidateRegister = "candidateRegister"
    35  	HandleCandidateUpdate   = "candidateUpdate"
    36  )
    37  
    38  const _withdrawWaitingTime = 14 * 24 * time.Hour // to maintain backward compatibility with r0.11 code
    39  
    40  // Errors and vars
    41  var (
    42  	ErrNilParameters = errors.New("parameter is nil")
    43  	errCandNotExist  = &handleError{
    44  		err:           ErrInvalidOwner,
    45  		failureStatus: iotextypes.ReceiptStatus_ErrCandidateNotExist,
    46  	}
    47  )
    48  
    49  type handleError struct {
    50  	err           error
    51  	failureStatus iotextypes.ReceiptStatus
    52  }
    53  
    54  func (h *handleError) Error() string {
    55  	return h.err.Error()
    56  }
    57  
    58  func (h *handleError) ReceiptStatus() uint64 {
    59  	return uint64(h.failureStatus)
    60  }
    61  
    62  func (p *Protocol) handleCreateStake(ctx context.Context, act *action.CreateStake, csm CandidateStateManager,
    63  ) (*receiptLog, []*action.TransactionLog, error) {
    64  	actionCtx := protocol.MustGetActionCtx(ctx)
    65  	blkCtx := protocol.MustGetBlockCtx(ctx)
    66  	featureCtx := protocol.MustGetFeatureCtx(ctx)
    67  	log := newReceiptLog(p.addr.String(), HandleCreateStake, featureCtx.NewStakingReceiptFormat)
    68  
    69  	staker, fetchErr := fetchCaller(ctx, csm, act.Amount())
    70  	if fetchErr != nil {
    71  		return log, nil, fetchErr
    72  	}
    73  
    74  	// Create new bucket and bucket index
    75  	candidate := csm.GetByName(act.Candidate())
    76  	if candidate == nil {
    77  		return log, nil, errCandNotExist
    78  	}
    79  	bucket := NewVoteBucket(candidate.Owner, actionCtx.Caller, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
    80  	bucketIdx, err := csm.putBucketAndIndex(bucket)
    81  	if err != nil {
    82  		return log, nil, err
    83  	}
    84  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), candidate.Owner.Bytes())
    85  
    86  	// update candidate
    87  	weightedVote := p.calculateVoteWeight(bucket, false)
    88  	if err := candidate.AddVote(weightedVote); err != nil {
    89  		return log, nil, &handleError{
    90  			err:           errors.Wrapf(err, "failed to add vote for candidate %s", candidate.Owner.String()),
    91  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketAmount,
    92  		}
    93  	}
    94  	if err := csm.Upsert(candidate); err != nil {
    95  		return log, nil, csmErrorToHandleError(candidate.Owner.String(), err)
    96  	}
    97  
    98  	// update bucket pool
    99  	if err := csm.DebitBucketPool(act.Amount(), true); err != nil {
   100  		return log, nil, &handleError{
   101  			err:           errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
   102  			failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
   103  		}
   104  	}
   105  
   106  	// update staker balance
   107  	if err := staker.SubBalance(act.Amount()); err != nil {
   108  		return log, nil, &handleError{
   109  			err:           errors.Wrapf(err, "failed to update the balance of staker %s", actionCtx.Caller.String()),
   110  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   111  		}
   112  	}
   113  	// put updated staker's account state to trie
   114  	if err := accountutil.StoreAccount(csm.SM(), actionCtx.Caller, staker); err != nil {
   115  		return log, nil, errors.Wrapf(err, "failed to store account %s", actionCtx.Caller.String())
   116  	}
   117  
   118  	log.AddAddress(candidate.Owner)
   119  	log.AddAddress(actionCtx.Caller)
   120  	log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx))
   121  
   122  	return log, []*action.TransactionLog{
   123  		{
   124  			Type:      iotextypes.TransactionLogType_CREATE_BUCKET,
   125  			Sender:    actionCtx.Caller.String(),
   126  			Recipient: address.StakingBucketPoolAddr,
   127  			Amount:    act.Amount(),
   128  		},
   129  	}, nil
   130  }
   131  
   132  func (p *Protocol) handleUnstake(ctx context.Context, act *action.Unstake, csm CandidateStateManager,
   133  ) (*receiptLog, error) {
   134  	actionCtx := protocol.MustGetActionCtx(ctx)
   135  	blkCtx := protocol.MustGetBlockCtx(ctx)
   136  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   137  	log := newReceiptLog(p.addr.String(), HandleUnstake, featureCtx.NewStakingReceiptFormat)
   138  
   139  	_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   140  	if fetchErr != nil {
   141  		return log, fetchErr
   142  	}
   143  
   144  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), true, true)
   145  	if fetchErr != nil {
   146  		return log, fetchErr
   147  	}
   148  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes())
   149  
   150  	candidate := csm.GetByOwner(bucket.Candidate)
   151  	if candidate == nil {
   152  		return log, errCandNotExist
   153  	}
   154  
   155  	if featureCtx.CannotUnstakeAgain && bucket.isUnstaked() {
   156  		return log, &handleError{
   157  			err:           errors.New("unstake an already unstaked bucket again not allowed"),
   158  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   159  		}
   160  	}
   161  
   162  	if bucket.AutoStake {
   163  		return log, &handleError{
   164  			err:           errors.New("AutoStake should be disabled first in order to unstake"),
   165  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   166  		}
   167  	}
   168  
   169  	if blkCtx.BlockTimeStamp.Before(bucket.StakeStartTime.Add(bucket.StakedDuration)) {
   170  		return log, &handleError{
   171  			err:           errors.New("bucket is not ready to be unstaked"),
   172  			failureStatus: iotextypes.ReceiptStatus_ErrUnstakeBeforeMaturity,
   173  		}
   174  	}
   175  	if !featureCtx.DisableDelegateEndorsement {
   176  		if rErr := validateBucketWithoutEndorsement(NewEndorsementStateManager(csm.SM()), bucket, blkCtx.BlockHeight); rErr != nil {
   177  			return log, rErr
   178  		}
   179  	}
   180  	// TODO: cannot unstake if selected as candidates in this or next epoch
   181  
   182  	// update bucket
   183  	bucket.UnstakeStartTime = blkCtx.BlockTimeStamp.UTC()
   184  	if err := csm.updateBucket(act.BucketIndex(), bucket); err != nil {
   185  		return log, errors.Wrapf(err, "failed to update bucket for voter %s", bucket.Owner.String())
   186  	}
   187  	selfStake, err := isSelfStakeBucket(featureCtx, csm, bucket)
   188  	if err != nil {
   189  		return log, &handleError{
   190  			err:           err,
   191  			failureStatus: iotextypes.ReceiptStatus_ErrUnknown,
   192  		}
   193  	}
   194  	weightedVote := p.calculateVoteWeight(bucket, selfStake)
   195  	if err := candidate.SubVote(weightedVote); err != nil {
   196  		return log, &handleError{
   197  			err:           errors.Wrapf(err, "failed to subtract vote for candidate %s", bucket.Candidate.String()),
   198  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   199  		}
   200  	}
   201  	// clear candidate's self stake if the bucket is self staking
   202  	if selfStake {
   203  		candidate.SelfStake = big.NewInt(0)
   204  	}
   205  	if err := csm.Upsert(candidate); err != nil {
   206  		return log, csmErrorToHandleError(candidate.Owner.String(), err)
   207  	}
   208  
   209  	log.AddAddress(actionCtx.Caller)
   210  	return log, nil
   211  }
   212  
   213  func (p *Protocol) handleWithdrawStake(ctx context.Context, act *action.WithdrawStake, csm CandidateStateManager,
   214  ) (*receiptLog, []*action.TransactionLog, error) {
   215  	actionCtx := protocol.MustGetActionCtx(ctx)
   216  	blkCtx := protocol.MustGetBlockCtx(ctx)
   217  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   218  	log := newReceiptLog(p.addr.String(), HandleWithdrawStake, featureCtx.NewStakingReceiptFormat)
   219  
   220  	withdrawer, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   221  	if fetchErr != nil {
   222  		return log, nil, fetchErr
   223  	}
   224  
   225  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), true, true)
   226  	if fetchErr != nil {
   227  		return log, nil, fetchErr
   228  	}
   229  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes())
   230  
   231  	// check unstake time
   232  	cannotWithdraw := bucket.UnstakeStartTime.Unix() == 0
   233  	if featureCtx.CannotUnstakeAgain {
   234  		cannotWithdraw = !bucket.isUnstaked()
   235  	}
   236  	if cannotWithdraw {
   237  		return log, nil, &handleError{
   238  			err:           errors.New("bucket has not been unstaked"),
   239  			failureStatus: iotextypes.ReceiptStatus_ErrWithdrawBeforeUnstake,
   240  		}
   241  	}
   242  
   243  	withdrawWaitTime := p.config.WithdrawWaitingPeriod
   244  	if !featureCtx.NewStakingReceiptFormat {
   245  		withdrawWaitTime = _withdrawWaitingTime
   246  	}
   247  	if blkCtx.BlockTimeStamp.Before(bucket.UnstakeStartTime.Add(withdrawWaitTime)) {
   248  		return log, nil, &handleError{
   249  			err:           errors.New("stake is not ready to withdraw"),
   250  			failureStatus: iotextypes.ReceiptStatus_ErrWithdrawBeforeMaturity,
   251  		}
   252  	}
   253  
   254  	// delete bucket and bucket index
   255  	if err := csm.delBucketAndIndex(bucket.Owner, bucket.Candidate, act.BucketIndex()); err != nil {
   256  		return log, nil, errors.Wrapf(err, "failed to delete bucket for candidate %s", bucket.Candidate.String())
   257  	}
   258  
   259  	// update bucket pool
   260  	if err := csm.CreditBucketPool(bucket.StakedAmount); err != nil {
   261  		return log, nil, &handleError{
   262  			err:           errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
   263  			failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
   264  		}
   265  	}
   266  
   267  	// update withdrawer balance
   268  	if err := withdrawer.AddBalance(bucket.StakedAmount); err != nil {
   269  		return log, nil, errors.Wrapf(err, "failed to add balance %s", bucket.StakedAmount)
   270  	}
   271  	// put updated withdrawer's account state to trie
   272  	if err := accountutil.StoreAccount(csm.SM(), actionCtx.Caller, withdrawer); err != nil {
   273  		return log, nil, errors.Wrapf(err, "failed to store account %s", actionCtx.Caller.String())
   274  	}
   275  
   276  	log.AddAddress(actionCtx.Caller)
   277  	if featureCtx.CannotUnstakeAgain {
   278  		log.SetData(bucket.StakedAmount.Bytes())
   279  	}
   280  
   281  	return log, []*action.TransactionLog{
   282  		{
   283  			Type:      iotextypes.TransactionLogType_WITHDRAW_BUCKET,
   284  			Sender:    address.StakingBucketPoolAddr,
   285  			Recipient: actionCtx.Caller.String(),
   286  			Amount:    bucket.StakedAmount,
   287  		},
   288  	}, nil
   289  }
   290  
   291  func (p *Protocol) handleChangeCandidate(ctx context.Context, act *action.ChangeCandidate, csm CandidateStateManager,
   292  ) (*receiptLog, error) {
   293  	actionCtx := protocol.MustGetActionCtx(ctx)
   294  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   295  	blkCtx := protocol.MustGetBlockCtx(ctx)
   296  	log := newReceiptLog(p.addr.String(), HandleChangeCandidate, featureCtx.NewStakingReceiptFormat)
   297  
   298  	_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   299  	if fetchErr != nil {
   300  		return log, fetchErr
   301  	}
   302  
   303  	candidate := csm.GetByName(act.Candidate())
   304  	if candidate == nil {
   305  		return log, errCandNotExist
   306  	}
   307  
   308  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), true, false)
   309  	if fetchErr != nil {
   310  		return log, fetchErr
   311  	}
   312  	if !featureCtx.DisableDelegateEndorsement {
   313  		if rErr := validateBucketWithoutEndorsement(NewEndorsementStateManager(csm.SM()), bucket, blkCtx.BlockHeight); rErr != nil {
   314  			return log, rErr
   315  		}
   316  	}
   317  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes(), candidate.Owner.Bytes())
   318  
   319  	prevCandidate := csm.GetByOwner(bucket.Candidate)
   320  	if prevCandidate == nil {
   321  		return log, errCandNotExist
   322  	}
   323  
   324  	if featureCtx.CannotUnstakeAgain && bucket.isUnstaked() {
   325  		return log, &handleError{
   326  			err:           errors.New("change candidate for an unstaked bucket not allowed"),
   327  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   328  		}
   329  	}
   330  
   331  	if featureCtx.CannotTranferToSelf && address.Equal(prevCandidate.Owner, candidate.Owner) {
   332  		// change to same candidate, do nothing
   333  		return log, &handleError{
   334  			err:           errors.New("change to same candidate"),
   335  			failureStatus: iotextypes.ReceiptStatus_ErrCandidateAlreadyExist,
   336  		}
   337  	}
   338  
   339  	// update bucket index
   340  	if err := csm.delCandBucketIndex(bucket.Candidate, act.BucketIndex()); err != nil {
   341  		return log, errors.Wrapf(err, "failed to delete candidate bucket index for candidate %s", bucket.Candidate.String())
   342  	}
   343  	if err := csm.putCandBucketIndex(candidate.Owner, act.BucketIndex()); err != nil {
   344  		return log, errors.Wrapf(err, "failed to put candidate bucket index for candidate %s", candidate.Owner.String())
   345  	}
   346  	// update bucket
   347  	bucket.Candidate = candidate.Owner
   348  	if err := csm.updateBucket(act.BucketIndex(), bucket); err != nil {
   349  		return log, errors.Wrapf(err, "failed to update bucket for voter %s", bucket.Owner.String())
   350  	}
   351  
   352  	// update previous candidate
   353  	weightedVotes := p.calculateVoteWeight(bucket, false)
   354  	if err := prevCandidate.SubVote(weightedVotes); err != nil {
   355  		return log, &handleError{
   356  			err:           errors.Wrapf(err, "failed to subtract vote for previous candidate %s", prevCandidate.Owner.String()),
   357  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   358  		}
   359  	}
   360  	// if the bucket equals to the previous candidate's self-stake bucket, it must be expired endorse bucket
   361  	// so we need to clear the self-stake of the previous candidate
   362  	if !featureCtx.DisableDelegateEndorsement && prevCandidate.SelfStakeBucketIdx == bucket.Index {
   363  		prevCandidate.SelfStake.SetInt64(0)
   364  		prevCandidate.SelfStakeBucketIdx = candidateNoSelfStakeBucketIndex
   365  	}
   366  	if err := csm.Upsert(prevCandidate); err != nil {
   367  		return log, csmErrorToHandleError(prevCandidate.Owner.String(), err)
   368  	}
   369  
   370  	// update current candidate
   371  	if err := candidate.AddVote(weightedVotes); err != nil {
   372  		return log, &handleError{
   373  			err:           errors.Wrapf(err, "failed to add vote for candidate %s", candidate.Owner.String()),
   374  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketAmount,
   375  		}
   376  	}
   377  	if err := csm.Upsert(candidate); err != nil {
   378  		return log, csmErrorToHandleError(candidate.Owner.String(), err)
   379  	}
   380  
   381  	log.AddAddress(candidate.Owner)
   382  	log.AddAddress(actionCtx.Caller)
   383  	return log, nil
   384  }
   385  
   386  func (p *Protocol) handleTransferStake(ctx context.Context, act *action.TransferStake, csm CandidateStateManager,
   387  ) (*receiptLog, error) {
   388  	actionCtx := protocol.MustGetActionCtx(ctx)
   389  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   390  	log := newReceiptLog(p.addr.String(), HandleTransferStake, featureCtx.NewStakingReceiptFormat)
   391  
   392  	_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   393  	if fetchErr != nil {
   394  		return log, fetchErr
   395  	}
   396  
   397  	newOwner := act.VoterAddress()
   398  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), true, false)
   399  	if fetchErr != nil {
   400  		if featureCtx.ReturnFetchError ||
   401  			fetchErr.ReceiptStatus() != uint64(iotextypes.ReceiptStatus_ErrUnauthorizedOperator) {
   402  			return log, fetchErr
   403  		}
   404  
   405  		// check whether the payload contains a valid consignment transfer
   406  		if consignment, ok := p.handleConsignmentTransfer(csm, ctx, act, bucket); ok {
   407  			newOwner = consignment.Transferee()
   408  		} else {
   409  			return log, fetchErr
   410  		}
   411  	}
   412  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), act.VoterAddress().Bytes(), bucket.Candidate.Bytes())
   413  
   414  	if featureCtx.CannotTranferToSelf && address.Equal(newOwner, bucket.Owner) {
   415  		// change to same owner, do nothing
   416  		return log, &handleError{
   417  			err:           errors.New("transfer to same owner"),
   418  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   419  		}
   420  	}
   421  
   422  	// update bucket index
   423  	if err := csm.delVoterBucketIndex(bucket.Owner, act.BucketIndex()); err != nil {
   424  		return log, errors.Wrapf(err, "failed to delete voter bucket index for voter %s", bucket.Owner.String())
   425  	}
   426  	if err := csm.putVoterBucketIndex(newOwner, act.BucketIndex()); err != nil {
   427  		return log, errors.Wrapf(err, "failed to put candidate bucket index for voter %s", act.VoterAddress().String())
   428  	}
   429  
   430  	// update bucket
   431  	bucket.Owner = newOwner
   432  	if err := csm.updateBucket(act.BucketIndex(), bucket); err != nil {
   433  		return log, errors.Wrapf(err, "failed to update bucket for voter %s", bucket.Owner.String())
   434  	}
   435  
   436  	log.AddAddress(actionCtx.Caller)
   437  	return log, nil
   438  }
   439  
   440  func (p *Protocol) handleConsignmentTransfer(
   441  	csm CandidateStateManager,
   442  	ctx context.Context,
   443  	act *action.TransferStake,
   444  	bucket *VoteBucket) (action.Consignment, bool) {
   445  	if len(act.Payload()) == 0 {
   446  		return nil, false
   447  	}
   448  
   449  	// self-stake cannot be transferred
   450  	actCtx := protocol.MustGetActionCtx(ctx)
   451  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   452  	selfStake, err := isSelfStakeBucket(featureCtx, csm, bucket)
   453  	if err != nil || selfStake {
   454  		return nil, false
   455  	}
   456  
   457  	con, err := action.NewConsignment(act.Payload())
   458  	if err != nil {
   459  		return nil, false
   460  	}
   461  
   462  	// a consignment transfer is valid if:
   463  	// (1) signer owns the bucket
   464  	// (2) designated transferee matches the action caller
   465  	// (3) designated asset ID matches bucket index
   466  	// (4) nonce matches the action caller's nonce
   467  	return con, address.Equal(con.Transferor(), bucket.Owner) &&
   468  		address.Equal(con.Transferee(), actCtx.Caller) &&
   469  		con.AssetID() == bucket.Index &&
   470  		con.TransfereeNonce() == actCtx.Nonce
   471  }
   472  
   473  func (p *Protocol) handleDepositToStake(ctx context.Context, act *action.DepositToStake, csm CandidateStateManager,
   474  ) (*receiptLog, []*action.TransactionLog, error) {
   475  	actionCtx := protocol.MustGetActionCtx(ctx)
   476  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   477  	log := newReceiptLog(p.addr.String(), HandleDepositToStake, featureCtx.NewStakingReceiptFormat)
   478  
   479  	depositor, fetchErr := fetchCaller(ctx, csm, act.Amount())
   480  	if fetchErr != nil {
   481  		return log, nil, fetchErr
   482  	}
   483  
   484  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), false, true)
   485  	if fetchErr != nil {
   486  		return log, nil, fetchErr
   487  	}
   488  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Owner.Bytes(), bucket.Candidate.Bytes())
   489  	if !bucket.AutoStake {
   490  		return log, nil, &handleError{
   491  			err:           errors.New("deposit is only allowed on auto-stake bucket"),
   492  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   493  		}
   494  	}
   495  	candidate := csm.GetByOwner(bucket.Candidate)
   496  	if candidate == nil {
   497  		return log, nil, errCandNotExist
   498  	}
   499  
   500  	if featureCtx.CannotUnstakeAgain && bucket.isUnstaked() {
   501  		return log, nil, &handleError{
   502  			err:           errors.New("deposit to an unstaked bucket not allowed"),
   503  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   504  		}
   505  	}
   506  	selfStake, err := isSelfStakeBucket(featureCtx, csm, bucket)
   507  	if err != nil {
   508  		return log, nil, &handleError{
   509  			err:           err,
   510  			failureStatus: iotextypes.ReceiptStatus_ErrUnknown,
   511  		}
   512  	}
   513  	prevWeightedVotes := p.calculateVoteWeight(bucket, selfStake)
   514  	// update bucket
   515  	bucket.StakedAmount.Add(bucket.StakedAmount, act.Amount())
   516  	if err := csm.updateBucket(act.BucketIndex(), bucket); err != nil {
   517  		return log, nil, errors.Wrapf(err, "failed to update bucket for voter %s", bucket.Owner.String())
   518  	}
   519  
   520  	// update candidate
   521  	if err := candidate.SubVote(prevWeightedVotes); err != nil {
   522  		return log, nil, &handleError{
   523  			err:           errors.Wrapf(err, "failed to subtract vote for candidate %s", bucket.Candidate.String()),
   524  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   525  		}
   526  	}
   527  	weightedVotes := p.calculateVoteWeight(bucket, selfStake)
   528  	if err := candidate.AddVote(weightedVotes); err != nil {
   529  		return log, nil, &handleError{
   530  			err:           errors.Wrapf(err, "failed to add vote for candidate %s", candidate.Owner.String()),
   531  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketAmount,
   532  		}
   533  	}
   534  	if selfStake {
   535  		if err := candidate.AddSelfStake(act.Amount()); err != nil {
   536  			return log, nil, &handleError{
   537  				err:           errors.Wrapf(err, "failed to add self stake for candidate %s", candidate.Owner.String()),
   538  				failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketAmount,
   539  			}
   540  		}
   541  	}
   542  	if err := csm.Upsert(candidate); err != nil {
   543  		return log, nil, csmErrorToHandleError(candidate.Owner.String(), err)
   544  	}
   545  
   546  	// update bucket pool
   547  	if err := csm.DebitBucketPool(act.Amount(), false); err != nil {
   548  		return log, nil, &handleError{
   549  			err:           errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
   550  			failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
   551  		}
   552  	}
   553  
   554  	// update depositor balance
   555  	if err := depositor.SubBalance(act.Amount()); err != nil {
   556  		return log, nil, &handleError{
   557  			err:           errors.Wrapf(err, "failed to update the balance of depositor %s", actionCtx.Caller.String()),
   558  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   559  		}
   560  	}
   561  	// put updated depositor's account state to trie
   562  	if err := accountutil.StoreAccount(csm.SM(), actionCtx.Caller, depositor); err != nil {
   563  		return log, nil, errors.Wrapf(err, "failed to store account %s", actionCtx.Caller.String())
   564  	}
   565  	log.AddAddress(actionCtx.Caller)
   566  
   567  	return log, []*action.TransactionLog{
   568  		{
   569  			Type:      iotextypes.TransactionLogType_DEPOSIT_TO_BUCKET,
   570  			Sender:    actionCtx.Caller.String(),
   571  			Recipient: address.StakingBucketPoolAddr,
   572  			Amount:    act.Amount(),
   573  		},
   574  	}, nil
   575  }
   576  
   577  func (p *Protocol) handleRestake(ctx context.Context, act *action.Restake, csm CandidateStateManager,
   578  ) (*receiptLog, error) {
   579  	actionCtx := protocol.MustGetActionCtx(ctx)
   580  	blkCtx := protocol.MustGetBlockCtx(ctx)
   581  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   582  	log := newReceiptLog(p.addr.String(), HandleRestake, featureCtx.NewStakingReceiptFormat)
   583  
   584  	_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   585  	if fetchErr != nil {
   586  		return log, fetchErr
   587  	}
   588  
   589  	bucket, fetchErr := p.fetchBucketAndValidate(featureCtx, csm, actionCtx.Caller, act.BucketIndex(), true, true)
   590  	if fetchErr != nil {
   591  		return log, fetchErr
   592  	}
   593  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes())
   594  
   595  	candidate := csm.GetByOwner(bucket.Candidate)
   596  	if candidate == nil {
   597  		return log, errCandNotExist
   598  	}
   599  
   600  	if featureCtx.CannotUnstakeAgain && bucket.isUnstaked() {
   601  		return log, &handleError{
   602  			err:           errors.New("restake an unstaked bucket not allowed"),
   603  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   604  		}
   605  	}
   606  	selfStake, err := isSelfStakeBucket(featureCtx, csm, bucket)
   607  	if err != nil {
   608  		return log, &handleError{
   609  			err:           err,
   610  			failureStatus: iotextypes.ReceiptStatus_ErrUnknown,
   611  		}
   612  	}
   613  	prevWeightedVotes := p.calculateVoteWeight(bucket, selfStake)
   614  	// update bucket
   615  	actDuration := time.Duration(act.Duration()) * 24 * time.Hour
   616  	if bucket.StakedDuration.Hours() > actDuration.Hours() {
   617  		// in case of reducing the duration
   618  		if bucket.AutoStake {
   619  			// if auto-stake on, user can't reduce duration
   620  			return log, &handleError{
   621  				err:           errors.New("AutoStake should be disabled first in order to reduce duration"),
   622  				failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   623  			}
   624  		} else if blkCtx.BlockTimeStamp.Before(bucket.StakeStartTime.Add(bucket.StakedDuration)) {
   625  			// if auto-stake off and maturity is not enough
   626  			return log, &handleError{
   627  				err:           errors.New("bucket is not ready to be able to reduce duration"),
   628  				failureStatus: iotextypes.ReceiptStatus_ErrReduceDurationBeforeMaturity,
   629  			}
   630  		}
   631  	}
   632  	bucket.StakedDuration = actDuration
   633  	bucket.StakeStartTime = blkCtx.BlockTimeStamp.UTC()
   634  	bucket.AutoStake = act.AutoStake()
   635  	if err := csm.updateBucket(act.BucketIndex(), bucket); err != nil {
   636  		return log, errors.Wrapf(err, "failed to update bucket for voter %s", bucket.Owner.String())
   637  	}
   638  
   639  	// update candidate
   640  	if err := candidate.SubVote(prevWeightedVotes); err != nil {
   641  		return log, &handleError{
   642  			err:           errors.Wrapf(err, "failed to subtract vote for candidate %s", bucket.Candidate.String()),
   643  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   644  		}
   645  	}
   646  	weightedVotes := p.calculateVoteWeight(bucket, selfStake)
   647  	if err := candidate.AddVote(weightedVotes); err != nil {
   648  		return log, &handleError{
   649  			err:           errors.Wrapf(err, "failed to add vote for candidate %s", candidate.Owner.String()),
   650  			failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketAmount,
   651  		}
   652  	}
   653  	if err := csm.Upsert(candidate); err != nil {
   654  		return log, csmErrorToHandleError(candidate.Owner.String(), err)
   655  	}
   656  
   657  	log.AddAddress(actionCtx.Caller)
   658  	return log, nil
   659  }
   660  
   661  func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.CandidateRegister, csm CandidateStateManager,
   662  ) (*receiptLog, []*action.TransactionLog, error) {
   663  	actCtx := protocol.MustGetActionCtx(ctx)
   664  	blkCtx := protocol.MustGetBlockCtx(ctx)
   665  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   666  	log := newReceiptLog(p.addr.String(), HandleCandidateRegister, featureCtx.NewStakingReceiptFormat)
   667  
   668  	registrationFee := new(big.Int).Set(p.config.RegistrationConsts.Fee)
   669  
   670  	caller, fetchErr := fetchCaller(ctx, csm, new(big.Int).Add(act.Amount(), registrationFee))
   671  	if fetchErr != nil {
   672  		return log, nil, fetchErr
   673  	}
   674  
   675  	owner := actCtx.Caller
   676  	if act.OwnerAddress() != nil {
   677  		owner = act.OwnerAddress()
   678  	}
   679  
   680  	c := csm.GetByOwner(owner)
   681  	ownerExist := c != nil
   682  	// cannot collide with existing owner (with selfstake != 0)
   683  	if ownerExist && c.SelfStake.Cmp(big.NewInt(0)) != 0 {
   684  		return log, nil, &handleError{
   685  			err:           ErrInvalidOwner,
   686  			failureStatus: iotextypes.ReceiptStatus_ErrCandidateAlreadyExist,
   687  		}
   688  	}
   689  	// cannot collide with existing name
   690  	if csm.ContainsName(act.Name()) && (!ownerExist || act.Name() != c.Name) {
   691  		return log, nil, &handleError{
   692  			err:           action.ErrInvalidCanName,
   693  			failureStatus: iotextypes.ReceiptStatus_ErrCandidateConflict,
   694  		}
   695  	}
   696  	// cannot collide with existing operator address
   697  	if csm.ContainsOperator(act.OperatorAddress()) &&
   698  		(!ownerExist || !address.Equal(act.OperatorAddress(), c.Operator)) {
   699  		return log, nil, &handleError{
   700  			err:           ErrInvalidOperator,
   701  			failureStatus: iotextypes.ReceiptStatus_ErrCandidateConflict,
   702  		}
   703  	}
   704  
   705  	var (
   706  		bucketIdx     uint64
   707  		votes         *big.Int
   708  		withSelfStake = act.Amount().Sign() > 0
   709  		txLogs        []*action.TransactionLog
   710  		err           error
   711  	)
   712  	if withSelfStake {
   713  		// register with self-stake
   714  		bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
   715  		bucketIdx, err = csm.putBucketAndIndex(bucket)
   716  		if err != nil {
   717  			return log, nil, err
   718  		}
   719  		txLogs = append(txLogs, &action.TransactionLog{
   720  			Type:      iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE,
   721  			Sender:    actCtx.Caller.String(),
   722  			Recipient: address.StakingBucketPoolAddr,
   723  			Amount:    act.Amount(),
   724  		})
   725  		votes = p.calculateVoteWeight(bucket, true)
   726  	} else {
   727  		// register w/o self-stake, waiting to be endorsed
   728  		bucketIdx = uint64(candidateNoSelfStakeBucketIndex)
   729  		votes = big.NewInt(0)
   730  	}
   731  	log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes())
   732  
   733  	c = &Candidate{
   734  		Owner:              owner,
   735  		Operator:           act.OperatorAddress(),
   736  		Reward:             act.RewardAddress(),
   737  		Name:               act.Name(),
   738  		Votes:              votes,
   739  		SelfStakeBucketIdx: bucketIdx,
   740  		SelfStake:          act.Amount(),
   741  	}
   742  
   743  	if err := csm.Upsert(c); err != nil {
   744  		return log, nil, csmErrorToHandleError(owner.String(), err)
   745  	}
   746  	height, _ := csm.SM().Height()
   747  	if p.needToWriteCandsMap(ctx, height) {
   748  		csm.DirtyView().candCenter.base.recordOwner(c)
   749  	}
   750  
   751  	if withSelfStake {
   752  		// update bucket pool
   753  		if err := csm.DebitBucketPool(act.Amount(), true); err != nil {
   754  			return log, nil, &handleError{
   755  				err:           errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
   756  				failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
   757  			}
   758  		}
   759  		// update caller balance
   760  		if err := caller.SubBalance(act.Amount()); err != nil {
   761  			return log, nil, &handleError{
   762  				err:           errors.Wrapf(err, "failed to update the balance of register %s", actCtx.Caller.String()),
   763  				failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   764  			}
   765  		}
   766  		// put updated caller's account state to trie
   767  		if err := accountutil.StoreAccount(csm.SM(), actCtx.Caller, caller); err != nil {
   768  			return log, nil, errors.Wrapf(err, "failed to store account %s", actCtx.Caller.String())
   769  		}
   770  	}
   771  
   772  	// put registrationFee to reward pool
   773  	if _, err := p.depositGas(ctx, csm.SM(), registrationFee); err != nil {
   774  		return log, nil, errors.Wrap(err, "failed to deposit gas")
   775  	}
   776  
   777  	log.AddAddress(owner)
   778  	log.AddAddress(actCtx.Caller)
   779  	log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx))
   780  
   781  	txLogs = append(txLogs, &action.TransactionLog{
   782  		Type:      iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE,
   783  		Sender:    actCtx.Caller.String(),
   784  		Recipient: address.RewardingPoolAddr,
   785  		Amount:    registrationFee,
   786  	})
   787  	return log, txLogs, nil
   788  }
   789  
   790  func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager,
   791  ) (*receiptLog, error) {
   792  	actCtx := protocol.MustGetActionCtx(ctx)
   793  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   794  	log := newReceiptLog(p.addr.String(), HandleCandidateUpdate, featureCtx.NewStakingReceiptFormat)
   795  
   796  	_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
   797  	if fetchErr != nil {
   798  		return log, fetchErr
   799  	}
   800  
   801  	// only owner can update candidate
   802  	c := csm.GetByOwner(actCtx.Caller)
   803  	if c == nil {
   804  		return log, errCandNotExist
   805  	}
   806  
   807  	if len(act.Name()) != 0 {
   808  		c.Name = act.Name()
   809  	}
   810  
   811  	if act.OperatorAddress() != nil {
   812  		c.Operator = act.OperatorAddress()
   813  	}
   814  
   815  	if act.RewardAddress() != nil {
   816  		c.Reward = act.RewardAddress()
   817  	}
   818  	log.AddTopics(c.Owner.Bytes())
   819  
   820  	if err := csm.Upsert(c); err != nil {
   821  		return log, csmErrorToHandleError(c.Owner.String(), err)
   822  	}
   823  	height, _ := csm.SM().Height()
   824  	if p.needToWriteCandsMap(ctx, height) {
   825  		csm.DirtyView().candCenter.base.recordOwner(c)
   826  	}
   827  
   828  	log.AddAddress(actCtx.Caller)
   829  	return log, nil
   830  }
   831  
   832  func (p *Protocol) fetchBucket(csm CandidateStateManager, index uint64) (*VoteBucket, ReceiptError) {
   833  	bucket, err := csm.getBucket(index)
   834  	if err != nil {
   835  		fetchErr := &handleError{
   836  			err:           errors.Wrapf(err, "failed to fetch bucket by index %d", index),
   837  			failureStatus: iotextypes.ReceiptStatus_Failure,
   838  		}
   839  		if errors.Cause(err) == state.ErrStateNotExist {
   840  			fetchErr.failureStatus = iotextypes.ReceiptStatus_ErrInvalidBucketIndex
   841  		}
   842  		return nil, fetchErr
   843  	}
   844  	return bucket, nil
   845  }
   846  
   847  func (p *Protocol) fetchBucketAndValidate(
   848  	featureCtx protocol.FeatureCtx,
   849  	csm CandidateStateManager,
   850  	caller address.Address,
   851  	index uint64,
   852  	checkOwner bool,
   853  	allowSelfStaking bool,
   854  ) (*VoteBucket, ReceiptError) {
   855  	bucket, err := p.fetchBucket(csm, index)
   856  	if err != nil {
   857  		return nil, err
   858  	}
   859  
   860  	// ReceiptStatus_ErrUnauthorizedOperator indicates action caller is not bucket owner
   861  	// upon return, the action will be subject to check whether it contains a valid consignment transfer
   862  	// do NOT return this value in case changes are added in the future
   863  	if checkOwner && !address.Equal(bucket.Owner, caller) {
   864  		return bucket, &handleError{
   865  			err:           errors.New("bucket owner does not match action caller"),
   866  			failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
   867  		}
   868  	}
   869  	if !allowSelfStaking {
   870  		selfStaking, serr := isSelfStakeBucket(featureCtx, csm, bucket)
   871  		if serr != nil {
   872  			return bucket, &handleError{
   873  				err:           serr,
   874  				failureStatus: iotextypes.ReceiptStatus_ErrUnknown,
   875  			}
   876  		}
   877  		if selfStaking {
   878  			return bucket, &handleError{
   879  				err:           errors.New("self staking bucket cannot be processed"),
   880  				failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
   881  			}
   882  		}
   883  	}
   884  	return bucket, nil
   885  }
   886  
   887  func fetchCaller(ctx context.Context, csm CandidateStateManager, amount *big.Int) (*state.Account, ReceiptError) {
   888  	actionCtx := protocol.MustGetActionCtx(ctx)
   889  	accountCreationOpts := []state.AccountCreationOption{}
   890  	if protocol.MustGetFeatureCtx(ctx).CreateLegacyNonceAccount {
   891  		accountCreationOpts = append(accountCreationOpts, state.LegacyNonceAccountTypeOption())
   892  	}
   893  	caller, err := accountutil.LoadAccount(csm.SM(), actionCtx.Caller, accountCreationOpts...)
   894  	if err != nil {
   895  		return nil, &handleError{
   896  			err:           errors.Wrapf(err, "failed to load the account of caller %s", actionCtx.Caller.String()),
   897  			failureStatus: iotextypes.ReceiptStatus_Failure,
   898  		}
   899  	}
   900  	gasFee := big.NewInt(0).Mul(actionCtx.GasPrice, big.NewInt(0).SetUint64(actionCtx.IntrinsicGas))
   901  	// check caller's balance
   902  	if !caller.HasSufficientBalance(new(big.Int).Add(amount, gasFee)) {
   903  		return nil, &handleError{
   904  			err:           errors.Wrapf(state.ErrNotEnoughBalance, "caller %s balance not enough", actionCtx.Caller.String()),
   905  			failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
   906  		}
   907  	}
   908  	return caller, nil
   909  }
   910  
   911  func csmErrorToHandleError(caller string, err error) error {
   912  	hErr := &handleError{
   913  		err: errors.Wrapf(err, "failed to put candidate %s", caller),
   914  	}
   915  
   916  	switch errors.Cause(err) {
   917  	case action.ErrInvalidCanName:
   918  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateConflict
   919  		return hErr
   920  	case ErrInvalidOperator:
   921  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateConflict
   922  		return hErr
   923  	case ErrInvalidSelfStkIndex:
   924  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateConflict
   925  		return hErr
   926  	case action.ErrInvalidAmount:
   927  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateNotExist
   928  		return hErr
   929  	case ErrInvalidOwner:
   930  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateNotExist
   931  		return hErr
   932  	case ErrInvalidReward:
   933  		hErr.failureStatus = iotextypes.ReceiptStatus_ErrCandidateNotExist
   934  		return hErr
   935  	default:
   936  		return err
   937  	}
   938  }
   939  
   940  // BucketIndexFromReceiptLog extracts bucket index from log
   941  func BucketIndexFromReceiptLog(log *iotextypes.Log) (uint64, bool) {
   942  	if log == nil || len(log.Topics) < 2 {
   943  		return 0, false
   944  	}
   945  
   946  	h := hash.Hash160b([]byte(_protocolID))
   947  	addr, _ := address.FromBytes(h[:])
   948  	if log.ContractAddress != addr.String() {
   949  		return 0, false
   950  	}
   951  
   952  	switch hash.BytesToHash256(log.Topics[0]) {
   953  	case hash.BytesToHash256([]byte(HandleCreateStake)), hash.BytesToHash256([]byte(HandleUnstake)),
   954  		hash.BytesToHash256([]byte(HandleWithdrawStake)), hash.BytesToHash256([]byte(HandleChangeCandidate)),
   955  		hash.BytesToHash256([]byte(HandleTransferStake)), hash.BytesToHash256([]byte(HandleDepositToStake)),
   956  		hash.BytesToHash256([]byte(HandleRestake)), hash.BytesToHash256([]byte(HandleCandidateRegister)):
   957  		return byteutil.BytesToUint64BigEndian(log.Topics[1][24:]), true
   958  	default:
   959  		return 0, false
   960  	}
   961  }