Skip to content

Commit

Permalink
[staking] Candidate Register without Staking (#4059)
Browse files Browse the repository at this point in the history
  • Loading branch information
envestcc authored Mar 11, 2024
1 parent ef4ae12 commit 9505d91
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 50 deletions.
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 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand
}
}

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)
}
log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes())

Expand All @@ -714,7 +735,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand
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 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand
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,
}
}
}

// 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,
}
}
// 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 @@ func SignedCandidateUpdate(
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 {
return nil, errors.Wrapf(err, "failed to sign candidate selfstake %v", elp)
}
return selp, nil
}

// 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 {
return nil, errors.Wrapf(err, "failed to sign candidate endorsement %v", elp)
}
return selp, nil
}

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

0 comments on commit 9505d91

Please sign in to comment.