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
DisableDelegateEndorsement bool
}

Expand Down Expand Up @@ -258,6 +259,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
ExecutionSizeLimit32KB: !g.IsSumatra(height),
UseZeroNonceForFreshAccount: g.IsSumatra(height),
SharedGasWithDapp: g.IsToBeEnabled(height),
CandidateRegisterMustWithStake: !g.IsToBeEnabled(height),
DisableDelegateEndorsement: !g.IsToBeEnabled(height),
},
)
Expand Down
87 changes: 51 additions & 36 deletions action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,10 +702,31 @@
}
}

bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
bucketIdx, err := csm.putBucketAndIndex(bucket)
if err != nil {
return log, nil, err
var (
bucketIdx uint64
votes *big.Int
withSelfStake = act.Amount().Sign() > 0
txLogs []*action.TransactionLog
err error
)
if withSelfStake {
// register with self-stake
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)
} else {
// register w/o self-stake, waiting to be endorsed
bucketIdx = uint64(candidateNoSelfStakeBucketIndex)
votes = big.NewInt(0)
}
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 @@ -714,7 +735,7 @@
Operator: act.OperatorAddress(),
Reward: act.RewardAddress(),
Name: act.Name(),
Votes: p.calculateVoteWeight(bucket, true),
Votes: votes,
SelfStakeBucketIdx: bucketIdx,
SelfStake: act.Amount(),
}
Expand All @@ -727,49 +748,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 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 756 in action/protocol/staking/handlers.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/staking/handlers.go#L754-L756

Added lines #L754 - L756 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 763 in action/protocol/staking/handlers.go

View check run for this annotation

Codecov / codecov/patch

action/protocol/staking/handlers.go#L761-L763

Added lines #L761 - L763 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
53 changes: 42 additions & 11 deletions action/protocol/staking/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,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 @@ -555,7 +576,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 @@ -572,16 +595,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
5 changes: 5 additions & 0 deletions action/protocol/staking/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"

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

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

if act.Amount().Cmp(p.config.RegistrationConsts.MinSelfStake) < 0 {
if !protocol.MustGetFeatureCtx(ctx).CandidateRegisterMustWithStake &&
act.Amount().Sign() == 0 {
return nil
}
return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid")
}
return nil
Expand Down
43 changes: 43 additions & 0 deletions action/signedaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,49 @@
return selp, nil
}

// SignedCandidateActivate returns a signed candidate selfstake
func SignedCandidateActivate(
nonce uint64,
bucketIndex uint64,
gasLimit uint64,
gasPrice *big.Int,
registererPriKey crypto.PrivateKey,
) (*SealedEnvelope, error) {
cu := NewCandidateActivate(nonce, gasLimit, gasPrice, bucketIndex)
bd := &EnvelopeBuilder{}
elp := bd.SetNonce(nonce).
SetGasPrice(gasPrice).
SetGasLimit(gasLimit).
SetAction(cu).Build()
selp, err := Sign(elp, registererPriKey)
if err != nil {

Check warning on line 125 in action/signedaction.go

View check run for this annotation

Codecov / codecov/patch

action/signedaction.go#L117-L125

Added lines #L117 - L125 were not covered by tests
return nil, errors.Wrapf(err, "failed to sign candidate selfstake %v", elp)
}
return selp, nil

Check warning on line 128 in action/signedaction.go

View check run for this annotation

Codecov / codecov/patch

action/signedaction.go#L128

Added line #L128 was not covered by tests
}

// SignedCandidateEndorsement returns a signed candidate endorsement
func SignedCandidateEndorsement(
nonce uint64,
bucketIndex uint64,
endorse bool,
gasLimit uint64,
gasPrice *big.Int,
registererPriKey crypto.PrivateKey,
) (*SealedEnvelope, error) {
cu := NewCandidateEndorsement(nonce, gasLimit, gasPrice, bucketIndex, endorse)
bd := &EnvelopeBuilder{}
elp := bd.SetNonce(nonce).
SetGasPrice(gasPrice).
SetGasLimit(gasLimit).
SetAction(cu).Build()
selp, err := Sign(elp, registererPriKey)
if err != nil {

Check warning on line 147 in action/signedaction.go

View check run for this annotation

Codecov / codecov/patch

action/signedaction.go#L139-L147

Added lines #L139 - L147 were not covered by tests
return nil, errors.Wrapf(err, "failed to sign candidate endorsement %v", elp)
}
return selp, nil

Check warning on line 150 in action/signedaction.go

View check run for this annotation

Codecov / codecov/patch

action/signedaction.go#L150

Added line #L150 was not covered by tests
}

// SignedCreateStake returns a signed create stake
func SignedCreateStake(nonce uint64,
candidateName, amount string,
Expand Down
Loading
Loading