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] fix weighted votes of contract staking bucket #3936

Merged
merged 11 commits into from
Oct 13, 2023

Conversation

envestcc
Copy link
Member

@envestcc envestcc commented Oct 10, 2023

Description

Currently, the IIP-13 contract staking bucket does not take into account the impact of the staking duration and stake-lock when calculating the voting power. It only considers the amount of IOTX staked. However, it should actually use the similar voting logic as the native staking bucket.

It's a hard-fork at RedseaBlockHeight.

Type of change

Please delete options that are not relevant.

  • [] Bug fix (non-breaking change which fixes an issue)
  • [] New feature (non-breaking change which adds functionality)
  • [] Code refactor or improvement
  • Breaking change (fix or feature that would cause a new or changed behavior of existing functionality)
  • [] This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • make test
  • [] fullsync
  • [] Other test (please specify)

Test Configuration:

  • Firmware version:
  • Hardware:
  • Toolchain:
  • SDK:

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

@codecov
Copy link

codecov bot commented Oct 10, 2023

Codecov Report

Merging #3936 (553fbd9) into master (e1f0636) will increase coverage by 0.71%.
Report is 86 commits behind head on master.
The diff coverage is 74.34%.

❗ Current head 553fbd9 differs from pull request most recent head abc14e6. Consider uploading reports for the commit abc14e6 to get more accurate results

@@            Coverage Diff             @@
##           master    #3936      +/-   ##
==========================================
+ Coverage   75.38%   76.09%   +0.71%     
==========================================
  Files         303      328      +25     
  Lines       25923    27959    +2036     
==========================================
+ Hits        19541    21275    +1734     
- Misses       5360     5591     +231     
- Partials     1022     1093      +71     
Files Coverage Δ
action/candidate_register.go 90.00% <100.00%> (ø)
action/candidate_update.go 89.01% <100.00%> (+0.12%) ⬆️
action/claimreward.go 90.62% <100.00%> (-0.42%) ⬇️
action/depositreward.go 87.50% <100.00%> (-0.56%) ⬇️
action/protocol/context.go 67.91% <100.00%> (+0.73%) ⬆️
action/protocol/execution/evm/evmstatedbadapter.go 66.77% <ø> (ø)
action/protocol/execution/protocol.go 42.10% <100.00%> (ø)
action/protocol/rewarding/reward.go 90.07% <100.00%> (+0.60%) ⬆️
...tion/protocol/staking/candidate_buckets_indexer.go 89.31% <100.00%> (-0.47%) ⬇️
...col/staking/ethabi/stake_composite_bucketscount.go 100.00% <100.00%> (ø)
... and 66 more

... and 5 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@envestcc envestcc marked this pull request as ready for review October 10, 2023 13:47
@envestcc envestcc requested review from CoderZhi, dustinxie, Liuhaai and a team as code owners October 10, 2023 13:47
remainingTime := v.StakedDuration.Seconds()
if !v.isNative() {
// according to produce one block per 5 seconds
remainingTime = float64(v.StakedDurationBlockNumber) * 5
Copy link
Collaborator

Choose a reason for hiding this comment

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

don't hard code constant

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, by maintain the StakedDuration field also for the nft bucket, we can remove the additional case here.

}
)

// NewContractStakingIndexer creates a new contract staking indexer
func NewContractStakingIndexer(kvStore db.KVStore, contractAddr string, contractDeployHeight uint64) (*Indexer, error) {
func NewContractStakingIndexer(kvStore db.KVStore, contractAddr string, contractDeployHeight uint64, voteConsts genesis.VoteWeightCalConsts) (*Indexer, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

pass in the calc function instead

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

remainingTime := v.StakedDuration.Seconds()
if !v.isNative() {
Copy link
Member

Choose a reason for hiding this comment

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

for NFT bucket, does it have v.StakedDuation?

Copy link
Member Author

Choose a reason for hiding this comment

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

This time, the StakedDuration field is also set for the NFT bucket, based on the StakedDurationBlockNumber* blockInterval.

}

calculateVoteWeightFunc func(v *Bucket, selfStake bool) *big.Int
Copy link
Member

Choose a reason for hiding this comment

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

since for NFT buckets, the func is always called withselfStake = false, can remove it from the input param?

calculateVoteWeightFunc func(v *Bucket) *big.Int

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, it's more concise

indexer, err := contractstaking.NewContractStakingIndexer(db.NewBoltDB(dbConfig), builder.cfg.Genesis.SystemStakingContractAddress, builder.cfg.Genesis.SystemStakingContractHeight)
voteCalcConsts := builder.cfg.Genesis.VoteWeightCalConsts
indexer, err := contractstaking.NewContractStakingIndexer(db.NewBoltDB(dbConfig), builder.cfg.Genesis.SystemStakingContractAddress, builder.cfg.Genesis.SystemStakingContractHeight, func(v *staking.VoteBucket, selfStake bool) *big.Int {
return staking.CalculateVoteWeight(voteCalcConsts, v, selfStake)
Copy link
Member

Choose a reason for hiding this comment

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

and here

return staking.CalculateVoteWeight(voteCalcConsts, v, false)

@@ -223,7 +228,7 @@ func calculateVoteWeight(c genesis.VoteWeightCalConsts, v *VoteBucket, selfStake
if remainingTime > 0 {
weight += math.Log(math.Ceil(remainingTime/86400)*(1+m)) / math.Log(c.DurationLg) / 100
}
if selfStake && v.AutoStake && v.StakedDuration >= time.Duration(91)*24*time.Hour {
if v.isNative() && selfStake && v.AutoStake && v.StakedDuration >= time.Duration(91)*24*time.Hour {
Copy link
Member

Choose a reason for hiding this comment

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

confused by the condition v.isNative() && selfStake. Does it mean contract can do self staking (isnative: false & selfstake: true) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Here, we just want to ensure that the nft bucket does not calculate selfStake weighting. However, this can also be achieved by ensuring that selfStake=false is passed in when bucket is nft. We can revert it.

@@ -137,7 +137,7 @@ func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager) (Candidate
}

if cand.SelfStakeBucketIdx == bucket.Index {
if err = cand.AddVote(calculateVoteWeight(vr.c, bucket, true)); err != nil {
if err = cand.AddVote(CalculateVoteWeight(vr.c, bucket, true)); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Will the returned result on L108 be impacted due to the behavior change of CalculateVoteWeight

@@ -74,7 +77,11 @@ func (s *contractStakingCache) CandidateVotes(candidate address.Address, height
continue
}
bt := s.mustGetBucketType(bi.TypeIndex)
votes.Add(votes, bt.Amount)
if featureCtx.FixContractStakingWeightedVotes {
votes.Add(votes, s.config.CalculateVoteWeight(assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval)))
Copy link
Member

Choose a reason for hiding this comment

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

throw error if CalculateVoteWeight is nullptr when Config is empty

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

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

// Bucket defines the bucket struct for contract staking
type Bucket = staking.VoteBucket

func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType, contractAddr string) *Bucket {
func assembleBucket(token uint64, bi *bucketInfo, bt *BucketType, contractAddr string, blockInterval time.Duration) *Bucket {
Copy link
Member

Choose a reason for hiding this comment

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

what are the values of CreateTime, StakeStartTime, and UnstakeStartTime?

Copy link
Member Author

Choose a reason for hiding this comment

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

they are only for native bucket and keep zero-value for nft bucket. The nft bucket use CreateBlockHeight, StakeStartBlockHeight, UnstakeStartBlockHeight instead.

@@ -1868,6 +1876,18 @@ func TestContractStaking(t *testing.T) {
})
})

t.Run("afterRedsea", func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

add tests on following heights, like _testRedseaBlockHeight + 10/ + 100, to indicates the change of weighted votes

Copy link
Member Author

Choose a reason for hiding this comment

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

the votes will not change over time unless unstake or restake.

ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{BlockHeight: height}))
votes, err := indexer.CandidateVotes(ctx, identityset.Address(7), height)
r.NoError(err)
r.EqualValues(12245, votes.Int64())
Copy link
Member

Choose a reason for hiding this comment

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

@dustinxie is the value 12245 expected?

@@ -74,7 +81,11 @@ func (s *contractStakingCache) CandidateVotes(candidate address.Address, height
continue
}
bt := s.mustGetBucketType(bi.TypeIndex)
votes.Add(votes, bt.Amount)
if featureCtx.FixContractStakingWeightedVotes {
votes.Add(votes, s.calculateVoteWeight(assembleBucket(id, bi, bt, s.contractAddress), false))
Copy link
Contributor

Choose a reason for hiding this comment

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

selfStake should check bucket ownerAddress == delegate,can not be use false here

Copy link
Member Author

Choose a reason for hiding this comment

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

selfStake is only for candidate register, not enable to nft bucket

vb := Bucket{
Index: token,
StakedAmount: bt.Amount,
StakedDuration: time.Duration(bt.Duration) * blockInterval,
Copy link
Member

@dustinxie dustinxie Oct 12, 2023

Choose a reason for hiding this comment

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

pls make sure all NFT buckets are processed by this assembleBucket before being passed to CalculateVoteWeight
otherwise v.StakedDuration is not correct

@@ -78,6 +78,9 @@ func (s *contractStakingCache) CandidateVotes(ctx context.Context, candidate add
}
bt := s.mustGetBucketType(bi.TypeIndex)
if featureCtx.FixContractStakingWeightedVotes {
if s.config.CalculateVoteWeight == nil {
return nil, errors.New("calculate vote weight function is not set")
}
Copy link
Member

Choose a reason for hiding this comment

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

if we add this check, it should be added at creation time, when the func is passed to Newxxx

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

@sonarqubecloud
Copy link

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 3 Code Smells

No Coverage information No Coverage information
14.0% 14.0% Duplication

idea Catch issues before they fail your Quality Gate with our IDE extension sonarlint SonarLint

@dustinxie dustinxie merged commit bd7b5e4 into iotexproject:master Oct 13, 2023
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants