Skip to content

Commit

Permalink
migrate stake
Browse files Browse the repository at this point in the history
  • Loading branch information
envestcc committed Jun 11, 2024
1 parent 296bf4d commit bd17ea8
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 25 deletions.
3 changes: 3 additions & 0 deletions action/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ func newStakingActionFromABIBinary(data []byte) (actionPayload, error) {
if act, err := NewCandidateTransferOwnershipFromABIBinary(data); err == nil {
return act, nil
}
if act, err := NewMigrateStakeFromABIBinary(data); err == nil {
return act, nil
}
return nil, ErrInvalidABI
}

Expand Down
2 changes: 2 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type (
SuicideTxLogMismatchPanic bool
PanicUnrecoverableError bool
CandidateIdentifiedByOwner bool
MigrateNativeStake bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -269,6 +270,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
SuicideTxLogMismatchPanic: g.IsToBeEnabled(height),
PanicUnrecoverableError: g.IsToBeEnabled(height),
CandidateIdentifiedByOwner: !g.IsToBeEnabled(height),
MigrateNativeStake: g.IsToBeEnabled(height),
},
)
}
Expand Down
20 changes: 20 additions & 0 deletions action/protocol/staking/contractstake_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,21 @@
package staking

import (
_ "embed"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/iotexproject/iotex-address/address"
)

var (
// StakingContractJSONABI is the abi json of staking contract
//go:embed contract_staking_abi.json
StakingContractJSONABI string
// StakingContractABI is the abi of staking contract
StakingContractABI abi.ABI
)

type (

// ContractStakingIndexer defines the interface of contract staking reader
Expand All @@ -31,3 +43,11 @@ type (
BucketTypes(height uint64) ([]*ContractStakingBucketType, error)
}
)

func init() {
var err error
StakingContractABI, err = abi.JSON(strings.NewReader(StakingContractJSONABI))
if err != nil {
panic(err)
}
}
181 changes: 181 additions & 0 deletions action/protocol/staking/handler_stake_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package staking

import (
"context"
"math/big"

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

"github.com/iotexproject/iotex-address/address"
"github.com/iotexproject/iotex-proto/golang/iotextypes"

"github.com/iotexproject/iotex-core/action"
"github.com/iotexproject/iotex-core/action/protocol"
accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
"github.com/iotexproject/iotex-core/pkg/log"
"github.com/iotexproject/iotex-core/pkg/util/byteutil"
"github.com/iotexproject/iotex-core/state"
)

const (
executionProtocolID = "smart_contract"
)

type (
executionProtocol interface {
Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error)
}
)

func (p *Protocol) handleStakeMigrate(ctx context.Context, act *action.MigrateStake, csm CandidateStateManager) ([]*action.Log, []*action.TransactionLog, uint64, error) {
actLogs := make([]*action.Log, 0)
transferLogs := make([]*action.TransactionLog, 0)
si := csm.SM().Snapshot()
revertSM := func() {
if revertErr := csm.SM().Revert(si); revertErr != nil {
log.L().Panic("failed to revert state", zap.Error(revertErr))
}
}

// validate bucket index
bucket, rErr := p.fetchBucket(csm, act.BucketIndex())
if rErr != nil {
return nil, nil, 0, rErr
}
if err := p.validateStakeMigrate(ctx, bucket, csm); err != nil {
return nil, nil, 0, err
}

// force-withdraw native bucket
staker, rerr := fetchCaller(ctx, csm, big.NewInt(0))
if rerr != nil {
return nil, nil, 0, errors.Wrap(rerr, "failed to fetch caller")
}
candidate := csm.GetByIdentifier(bucket.Candidate)
if candidate == nil {
return nil, nil, 0, errCandNotExist
}
actLog, tLog, err := p.withdrawBucket(ctx, staker, bucket, candidate, csm)
if err != nil {
return nil, nil, 0, err
}
actLogs = append(actLogs, actLog.Build(ctx, nil))
transferLogs = append(transferLogs, tLog)

// call staking contract to stake
duration := int64(bucket.StakedDuration / p.getBlockInterval(protocol.MustGetBlockCtx(ctx).BlockHeight))
excReceipt, err := p.createNFTBucket(ctx, act, bucket.StakedAmount, big.NewInt(duration), candidate, csm.SM())
if err != nil {
revertSM()
return nil, nil, 0, errors.Wrap(err, "failed to handle execution action")
}
if excReceipt.Status != uint64(iotextypes.ReceiptStatus_Success) {
// TODO: return err or handle error?
revertSM()
return nil, nil, 0, errors.Errorf("execution failed with status %d", excReceipt.Status)
}
// add sub-receipts logs
actLogs = append(actLogs, excReceipt.Logs()...)
transferLogs = append(transferLogs, excReceipt.TransactionLogs()...)
return actLogs, transferLogs, excReceipt.GasConsumed, nil
}

func (p *Protocol) validateStakeMigrate(ctx context.Context, bucket *VoteBucket, csm CandidateStateManager) error {
if err := validateBucketOwner(bucket, protocol.MustGetActionCtx(ctx).Caller); err != nil {
return err
}
if !bucket.AutoStake {
return &handleError{
err: errors.New("cannot migrate non-auto-staked bucket"),
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
}
}
if bucket.isUnstaked() {
return &handleError{
err: errors.New("cannot migrate unstaked bucket"),
failureStatus: iotextypes.ReceiptStatus_ErrInvalidBucketType,
}
}
if err := validateBucketWithoutEndorsement(NewEndorsementStateManager(csm.SM()), bucket, protocol.MustGetBlockCtx(ctx).BlockHeight); err != nil {
return err
}
return validateBucketSelfStake(protocol.MustGetFeatureCtx(ctx), csm, bucket, false)
}

func (p *Protocol) withdrawBucket(ctx context.Context, withdrawer *state.Account, bucket *VoteBucket, cand *Candidate, csm CandidateStateManager) (*receiptLog, *action.TransactionLog, error) {
// delete bucket and bucket index
if err := csm.delBucketAndIndex(bucket.Owner, bucket.Candidate, bucket.Index); err != nil {
return nil, nil, errors.Wrapf(err, "failed to delete bucket for candidate %s", bucket.Candidate.String())
}

// update bucket pool
if err := csm.CreditBucketPool(bucket.StakedAmount); err != nil {
return nil, nil, errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error())
}
// update candidate vote
weightedVote := p.calculateVoteWeight(bucket, false)
if err := cand.SubVote(weightedVote); err != nil {
return nil, nil, &handleError{
err: errors.Wrapf(err, "failed to subtract vote for candidate %s", bucket.Candidate.String()),
failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
}
}
// clear candidate's self stake if the
if cand.SelfStakeBucketIdx == bucket.Index {
cand.SelfStake = big.NewInt(0)
cand.SelfStakeBucketIdx = candidateNoSelfStakeBucketIndex
}
if err := csm.Upsert(cand); err != nil {
return nil, nil, csmErrorToHandleError(cand.GetIdentifier().String(), err)
}
// update withdrawer balance
if err := withdrawer.AddBalance(bucket.StakedAmount); err != nil {
return nil, nil, errors.Wrapf(err, "failed to add balance %s", bucket.StakedAmount)
}
// put updated withdrawer's account state to trie
actionCtx := protocol.MustGetActionCtx(ctx)
if err := accountutil.StoreAccount(csm.SM(), actionCtx.Caller, withdrawer); err != nil {
return nil, nil, errors.Wrapf(err, "failed to store account %s", actionCtx.Caller.String())
}
// create receipt log
actLog := newReceiptLog(p.addr.String(), handleCandidateActivate, protocol.MustGetFeatureCtx(ctx).NewStakingReceiptFormat)
actLog.AddTopics(byteutil.Uint64ToBytesBigEndian(bucket.Index), bucket.Candidate.Bytes())
actLog.AddAddress(actionCtx.Caller)
actLog.SetData(bucket.StakedAmount.Bytes())
return actLog, &action.TransactionLog{
Type: iotextypes.TransactionLogType_WITHDRAW_BUCKET,
Amount: bucket.StakedAmount,
Sender: address.StakingBucketPoolAddr,
Recipient: actionCtx.Caller.String(),
}, nil
}

func (p *Protocol) createNFTBucket(ctx context.Context, act *action.MigrateStake, amount, duration *big.Int, cand *Candidate, sm protocol.StateManager) (*action.Receipt, error) {
ptl, ok := protocol.MustGetRegistry(ctx).Find(executionProtocolID)
if !ok {
return nil, errors.New("execution protocol is not registered")
}
exctPtl, ok := ptl.(executionProtocol)
contractAddress := p.config.MigrateContractAddress
data, err := StakingContractABI.Pack("stake0", duration, cand.GetIdentifier())
if err != nil {
return nil, errors.Wrap(err, "failed to pack data for contract call")
}
exeAct, err := action.NewExecution(
contractAddress,
act.Nonce(),
amount,
act.GasLimit(),
act.GasPrice(),
data,
)
if err != nil {
return nil, errors.Wrap(err, "failed to create execution action")
}
excReceipt, err := exctPtl.Handle(ctx, exeAct, sm)
if err != nil {
return nil, errors.Wrap(err, "failed to handle execution action")
}
return excReceipt, nil
}
30 changes: 20 additions & 10 deletions action/protocol/staking/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type (
contractStakingIndexer ContractStakingIndexerWithBucketType
voteReviser *VoteReviser
patch *PatchStore
getBlockInterval func(uint64) time.Duration
}

// Configuration is the staking protocol configuration.
Expand All @@ -94,6 +95,7 @@ type (
BootstrapCandidates []genesis.BootstrapCandidate
PersistStakingPatchBlock uint64
EndorsementWithdrawWaitingBlocks uint64
MigrateContractAddress string
}

// DepositGas deposits gas to some pool
Expand Down Expand Up @@ -396,10 +398,12 @@ func (p *Protocol) Handle(ctx context.Context, act action.Action, sm protocol.St

func (p *Protocol) handle(ctx context.Context, act action.Action, csm CandidateStateManager) (*action.Receipt, error) {
var (
rLog *receiptLog
tLogs []*action.TransactionLog
err error
logs []*action.Log
rLog *receiptLog
tLogs []*action.TransactionLog
err error
logs []*action.Log
actionCtx = protocol.MustGetActionCtx(ctx)
gasConsumed = actionCtx.IntrinsicGas
)

switch act := act.(type) {
Expand Down Expand Up @@ -427,22 +431,25 @@ func (p *Protocol) handle(ctx context.Context, act action.Action, csm CandidateS
rLog, tLogs, err = p.handleCandidateEndorsement(ctx, act, csm)
case *action.CandidateTransferOwnership:
rLog, tLogs, err = p.handleCandidateTransferOwnership(ctx, act, csm)
case *action.MigrateStake:
logs, tLogs, gasConsumed, err = p.handleStakeMigrate(ctx, act, csm)
default:
return nil, nil
}

if l := rLog.Build(ctx, err); l != nil {
logs = append(logs, l)
if rLog != nil {
if l := rLog.Build(ctx, err); l != nil {
logs = append(logs, l)
}
}
if err == nil {
return p.settleAction(ctx, csm.SM(), uint64(iotextypes.ReceiptStatus_Success), logs, tLogs)
return p.settleAction(ctx, csm.SM(), uint64(iotextypes.ReceiptStatus_Success), logs, tLogs, gasConsumed)
}

if receiptErr, ok := err.(ReceiptError); ok {
actionCtx := protocol.MustGetActionCtx(ctx)
log.L().With(
zap.String("actionHash", hex.EncodeToString(actionCtx.ActionHash[:]))).Debug("Failed to commit staking action", zap.Error(err))
return p.settleAction(ctx, csm.SM(), receiptErr.ReceiptStatus(), logs, tLogs)
return p.settleAction(ctx, csm.SM(), receiptErr.ReceiptStatus(), logs, tLogs, gasConsumed)
}
return nil, err
}
Expand Down Expand Up @@ -477,6 +484,8 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol.
return p.validateCandidateEndorsement(ctx, act)
case *action.CandidateTransferOwnership:
return p.validateCandidateTransferOwnershipAction(ctx, act)
case *action.MigrateStake:
return p.validateMigrateStake(ctx, act)
}
return nil
}
Expand Down Expand Up @@ -649,6 +658,7 @@ func (p *Protocol) settleAction(
status uint64,
logs []*action.Log,
tLogs []*action.TransactionLog,
gasConsumed uint64,
) (*action.Receipt, error) {
actionCtx := protocol.MustGetActionCtx(ctx)
blkCtx := protocol.MustGetBlockCtx(ctx)
Expand All @@ -675,7 +685,7 @@ func (p *Protocol) settleAction(
Status: status,
BlockHeight: blkCtx.BlockHeight,
ActionHash: actionCtx.ActionHash,
GasConsumed: actionCtx.IntrinsicGas,
GasConsumed: gasConsumed,
ContractAddress: p.addr.String(),
}
r.AddLogs(logs...).AddTransactionLogs(depositLog).AddTransactionLogs(tLogs...)
Expand Down
7 changes: 7 additions & 0 deletions action/protocol/staking/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,10 @@ func (p *Protocol) validateCandidateTransferOwnershipAction(ctx context.Context,
}
return nil
}

func (p *Protocol) validateMigrateStake(ctx context.Context, act *action.MigrateStake) error {
if !protocol.MustGetFeatureCtx(ctx).MigrateNativeStake {
return errors.Wrap(action.ErrInvalidAct, "migrate stake is disabled")
}
return nil
}
Loading

0 comments on commit bd17ea8

Please sign in to comment.