Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[staking] Candidate Register without Staking #4059

Merged
merged 12 commits into from
Mar 11, 2024
6 changes: 3 additions & 3 deletions action/candidate_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/iotexproject/iotex-address/address"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"

"github.com/iotexproject/iotex-address/address"
"github.com/iotexproject/iotex-core/pkg/util/byteutil"
"github.com/iotexproject/iotex-core/pkg/version"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
)

const (
Expand Down Expand Up @@ -290,7 +290,7 @@ func (cr *CandidateRegister) Cost() (*big.Int, error) {

// SanityCheck validates the variables in the action
func (cr *CandidateRegister) SanityCheck() error {
if cr.Amount().Sign() <= 0 {
if cr.Amount().Sign() < 0 {
return errors.Wrap(ErrInvalidAmount, "negative value")
}
if !IsValidCandidateName(cr.Name()) {
Expand Down
2 changes: 2 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ type (
ExecutionSizeLimit32KB bool
UseZeroNonceForFreshAccount bool
SharedGasWithDapp bool
CandidateRegisterMustWithStake bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -257,6 +258,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
ExecutionSizeLimit32KB: !g.IsSumatra(height),
UseZeroNonceForFreshAccount: g.IsSumatra(height),
SharedGasWithDapp: g.IsToBeEnabled(height),
CandidateRegisterMustWithStake: !g.IsToBeEnabled(height),
},
)
}
Expand Down
82 changes: 46 additions & 36 deletions action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,10 +663,26 @@
}
}

bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
bucketIdx, err := csm.putBucketAndIndex(bucket)
if err != nil {
return log, nil, err
// register with self-stake
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to L713

var (
bucketIdx = uint64(candidateNoSelfStakeBucketIndex)
txLogs []*action.TransactionLog
votes = big.NewInt(0)
err error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. withSelfStake = act.Amount().Sign() > 0
  2. err error not ncessary?

)
if act.Amount().Sign() > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no hardfork here?

Copy link
Member Author

@envestcc envestcc Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before the hard fork, the act.Amount must be greater than 0 (garunteed by action validation), behaves the same as in the old version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if act.Amount().Sign() <= 0 { should be return error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if withSelfStake {

bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
bucketIdx, err = csm.putBucketAndIndex(bucket)
if err != nil {
return log, nil, err
}
txLogs = append(txLogs, &action.TransactionLog{
Type: iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE,
Sender: actCtx.Caller.String(),
Recipient: address.StakingBucketPoolAddr,
Amount: act.Amount(),
})
votes = p.calculateVoteWeight(bucket, true)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think adding an else make the code more explicit

else {
    // register w/o self-stake, waiting to be endorsed
   bucketIdx = uint64(candidateNoSelfStakeBucketIndex)
   votes  = big.NewInt(0)
}

log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes())

Expand All @@ -675,7 +691,7 @@
Operator: act.OperatorAddress(),
Reward: act.RewardAddress(),
Name: act.Name(),
Votes: p.calculateVoteWeight(bucket, true),
Votes: votes,
SelfStakeBucketIdx: bucketIdx,
SelfStake: act.Amount(),
}
Expand All @@ -688,49 +704,43 @@
csm.DirtyView().candCenter.base.recordOwner(c)
}

// update bucket pool
if err := csm.DebitBucketPool(act.Amount(), true); err != nil {
return log, nil, &handleError{
err: errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
if act.Amount().Sign() > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if withSelfStake {

// update bucket pool
if err := csm.DebitBucketPool(act.Amount(), true); err != nil {
return log, nil, &handleError{
err: errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,

Check warning on line 712 in action/protocol/staking/handlers.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/staking/handlers.go#L710-L712

Added lines #L710 - L712 were not covered by tests
}
}
}

// update caller balance
if err := caller.SubBalance(act.Amount()); err != nil {
return log, nil, &handleError{
err: errors.Wrapf(err, "failed to update the balance of register %s", actCtx.Caller.String()),
failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
// update caller balance
if err := caller.SubBalance(act.Amount()); err != nil {
return log, nil, &handleError{
err: errors.Wrapf(err, "failed to update the balance of register %s", actCtx.Caller.String()),
failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,

Check warning on line 719 in action/protocol/staking/handlers.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/staking/handlers.go#L717-L719

Added lines #L717 - L719 were not covered by tests
}
}
// put updated caller's account state to trie
if err := accountutil.StoreAccount(csm.SM(), actCtx.Caller, caller); err != nil {
return log, nil, errors.Wrapf(err, "failed to store account %s", actCtx.Caller.String())
}
}
// put updated caller's account state to trie
if err := accountutil.StoreAccount(csm.SM(), actCtx.Caller, caller); err != nil {
return log, nil, errors.Wrapf(err, "failed to store account %s", actCtx.Caller.String())
}

// put registrationFee to reward pool
if _, err = p.depositGas(ctx, csm.SM(), registrationFee); err != nil {
if _, err := p.depositGas(ctx, csm.SM(), registrationFee); err != nil {
return log, nil, errors.Wrap(err, "failed to deposit gas")
}

log.AddAddress(owner)
log.AddAddress(actCtx.Caller)
log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx))

return log, []*action.TransactionLog{
{
Type: iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE,
Sender: actCtx.Caller.String(),
Recipient: address.StakingBucketPoolAddr,
Amount: act.Amount(),
},
{
Type: iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE,
Sender: actCtx.Caller.String(),
Recipient: address.RewardingPoolAddr,
Amount: registrationFee,
},
}, nil
txLogs = append(txLogs, &action.TransactionLog{
Type: iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE,
Sender: actCtx.Caller.String(),
Recipient: address.RewardingPoolAddr,
Amount: registrationFee,
})
return log, txLogs, nil
}

func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager,
Expand Down
54 changes: 43 additions & 11 deletions action/protocol/staking/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/golang/mock/gomock"
"github.com/mohae/deepcopy"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -531,6 +532,27 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) {
nil,
iotextypes.ReceiptStatus_ErrCandidateConflict,
},
// register without self-stake
{
1201000,
identityset.Address(27),
1,
"test",
identityset.Address(28).String(),
identityset.Address(29).String(),
identityset.Address(30).String(),
"0",
"0",
uint32(10000),
false,
nil,
uint64(1000000),
uint64(1000000),
big.NewInt(1),
true,
nil,
iotextypes.ReceiptStatus_Success,
},
}

for _, test := range tests {
Expand All @@ -553,7 +575,9 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) {
BlockTimeStamp: time.Now(),
GasLimit: test.blkGasLimit,
})
ctx = genesis.WithGenesisContext(ctx, genesis.Default)
g := deepcopy.Copy(genesis.Default).(genesis.Genesis)
g.ToBeEnabledBlockHeight = 0
ctx = genesis.WithGenesisContext(ctx, g)
ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm)))
if test.err != nil {
Expand All @@ -570,16 +594,24 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) {
if test.err == nil && test.status == iotextypes.ReceiptStatus_Success {
// check the special create bucket and candidate register log
tLogs := r.TransactionLogs()
require.Equal(2, len(tLogs))
cLog := tLogs[0]
require.Equal(test.caller.String(), cLog.Sender)
require.Equal(address.StakingBucketPoolAddr, cLog.Recipient)
require.Equal(test.amountStr, cLog.Amount.String())

cLog = tLogs[1]
require.Equal(test.caller.String(), cLog.Sender)
require.Equal(address.RewardingPoolAddr, cLog.Recipient)
require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String())
if test.amountStr == "0" {
require.Equal(1, len(tLogs))
cLog := tLogs[0]
require.Equal(test.caller.String(), cLog.Sender)
require.Equal(address.RewardingPoolAddr, cLog.Recipient)
require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String())
} else {
require.Equal(2, len(tLogs))
cLog := tLogs[0]
require.Equal(test.caller.String(), cLog.Sender)
require.Equal(address.StakingBucketPoolAddr, cLog.Recipient)
require.Equal(test.amountStr, cLog.Amount.String())

cLog = tLogs[1]
require.Equal(test.caller.String(), cLog.Sender)
require.Equal(address.RewardingPoolAddr, cLog.Recipient)
require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String())
}

// test candidate
candidate, _, err := csr.getCandidate(act.OwnerAddress())
Expand Down
10 changes: 9 additions & 1 deletion action/protocol/staking/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ package staking

import (
"context"
"math/big"

"github.com/pkg/errors"
"go.uber.org/zap"

"github.com/iotexproject/iotex-core/action"
"github.com/iotexproject/iotex-core/action/protocol"
"github.com/iotexproject/iotex-core/pkg/log"
)

// Errors
Expand Down Expand Up @@ -66,7 +70,11 @@ func (p *Protocol) validateCandidateRegister(ctx context.Context, act *action.Ca
}

if act.Amount().Cmp(p.config.RegistrationConsts.MinSelfStake) < 0 {
return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid")
featureCtx := protocol.MustGetFeatureCtx(ctx)
if featureCtx.CandidateRegisterMustWithStake || act.Amount().Cmp(big.NewInt(0)) != 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if featureCtx.CandidateRegisterMustWithStake always return error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, only featureCtx.CandidateRegisterMustWithStake==false && act.Amount()==0 is valid if act.Amount() < MinSelfStake

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!featureCtx.CandidateRegisterMustWithStake && act.Amount()==0 is easier to read than featureCtx.CandidateRegisterMustWithStake || act.Amount().Cmp(big.NewInt(0)) != 0

log.L().Warn("Candidate register amount is less than the minimum requirement", zap.Bool("feature", featureCtx.CandidateRegisterMustWithStake), zap.String("amount", act.Amount().String()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the error has been logged upstream

return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid")
}
}
return nil
}
Expand Down
Loading