Skip to content

Commit

Permalink
[reward] extend foundation bonus (#2785)
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinxie committed Sep 30, 2021
1 parent baa0a92 commit ebe895c
Show file tree
Hide file tree
Showing 18 changed files with 543 additions and 248 deletions.
2 changes: 1 addition & 1 deletion action/protocol/account/transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestProtocol_HandleTransfer(t *testing.T) {

// set-up protocol and genesis states
p := NewProtocol(rewarding.DepositGas)
reward := rewarding.NewProtocol(0, 0)
reward := rewarding.NewProtocol(config.Default.Genesis.Rewarding)
registry := protocol.NewRegistry()
require.NoError(reward.Register(registry))
chainCtx := genesis.WithGenesisContext(
Expand Down
2 changes: 1 addition & 1 deletion action/protocol/execution/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ func (sct *SmartContractTest) prepareBlockchain(
protocol.NewGenericValidator(sf, accountutil.AccountState),
)),
)
reward := rewarding.NewProtocol(0, 0)
reward := rewarding.NewProtocol(cfg.Genesis.Rewarding)
r.NoError(reward.Register(registry))

r.NotNil(bc)
Expand Down
67 changes: 53 additions & 14 deletions action/protocol/rewarding/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type admin struct {
numDelegatesForFoundationBonus uint64
foundationBonusLastEpoch uint64
productivityThreshold uint64
foundationBonusExtension []genesis.Period
}

// Serialize serializes admin state into bytes
Expand All @@ -40,10 +41,26 @@ func (a admin) Serialize() ([]byte, error) {
NumDelegatesForFoundationBonus: a.numDelegatesForFoundationBonus,
FoundationBonusLastEpoch: a.foundationBonusLastEpoch,
ProductivityThreshold: a.productivityThreshold,
FoundationBonusExtension: a.periodProto(),
}
return proto.Marshal(&gen)
}

func (a *admin) periodProto() []*rewardingpb.Period {
if len(a.foundationBonusExtension) == 0 {
return nil
}

pb := make([]*rewardingpb.Period, len(a.foundationBonusExtension))
for i, ext := range a.foundationBonusExtension {
pb[i] = &rewardingpb.Period{
Start: ext.Start,
End: ext.End,
}
}
return pb
}

// Deserialize deserializes bytes into admin state
func (a *admin) Deserialize(data []byte) error {
gen := rewardingpb.Admin{}
Expand All @@ -69,9 +86,37 @@ func (a *admin) Deserialize(data []byte) error {
a.numDelegatesForFoundationBonus = gen.NumDelegatesForFoundationBonus
a.foundationBonusLastEpoch = gen.FoundationBonusLastEpoch
a.productivityThreshold = gen.ProductivityThreshold

if len(gen.FoundationBonusExtension) == 0 {
return nil
}
a.foundationBonusExtension = make([]genesis.Period, len(gen.FoundationBonusExtension))
for i, v := range gen.FoundationBonusExtension {
a.foundationBonusExtension[i] = genesis.Period{
Start: v.Start,
End: v.End,
}
}
return nil
}

func (a *admin) hasFoundationBonusExtension() bool {
// starting Kamchatka height, we add the foundation bonus extension epoch into admin{} struct
return len(a.foundationBonusExtension) > 0
}

func (a *admin) grantFoundationBonus(epoch uint64) bool {
if epoch <= a.foundationBonusLastEpoch {
return true
}
for _, ext := range a.foundationBonusExtension {
if epoch >= ext.Start && epoch <= ext.End {
return true
}
}
return false
}

// exempt stores the addresses that exempt from epoch reward
type exempt struct {
addrs []address.Address
Expand Down Expand Up @@ -124,30 +169,24 @@ func (p *Protocol) CreateGenesisStates(
return err
}

initBalance := g.InitBalance()
numDelegatesForEpochReward := g.NumDelegatesForEpochReward
exemptAddrs := g.ExemptAddrsFromEpochReward()
foundationBonus := g.FoundationBonus()
numDelegatesForFoundationBonus := g.NumDelegatesForFoundationBonus
foundationBonusLastEpoch := g.FoundationBonusLastEpoch
productivityThreshold := g.ProductivityThreshold

if err := p.putState(
ctx,
sm,
adminKey,
&admin{
blockReward: blockReward,
epochReward: epochReward,
numDelegatesForEpochReward: numDelegatesForEpochReward,
foundationBonus: foundationBonus,
numDelegatesForFoundationBonus: numDelegatesForFoundationBonus,
foundationBonusLastEpoch: foundationBonusLastEpoch,
productivityThreshold: productivityThreshold,
numDelegatesForEpochReward: g.NumDelegatesForEpochReward,
foundationBonus: g.FoundationBonus(),
numDelegatesForFoundationBonus: g.NumDelegatesForFoundationBonus,
foundationBonusLastEpoch: g.FoundationBonusLastEpoch,
productivityThreshold: g.ProductivityThreshold,
},
); err != nil {
return err
}

initBalance := g.InitBalance()
if err := p.putState(
ctx,
sm,
Expand All @@ -164,7 +203,7 @@ func (p *Protocol) CreateGenesisStates(
sm,
exemptKey,
&exempt{
addrs: exemptAddrs,
addrs: g.ExemptAddrsFromEpochReward(),
},
)
}
Expand Down
33 changes: 33 additions & 0 deletions action/protocol/rewarding/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,48 @@ package rewarding

import (
"context"
"encoding/hex"
"math/big"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

func TestAdminPb(t *testing.T) {
r := require.New(t)

// actual data of admin.v1 on mainnet
b, err := hex.DecodeString("0a133830303030303030303030303030303030303012173138373530303030303030303030303030303030303030186422143830303030303030303030303030303030303030282430b8443855")
r.NoError(err)
a := admin{}
r.NoError(a.Deserialize(b))

g := genesis.Default
r.Equal(a.blockReward.String(), g.DardanellesBlockRewardStr)
r.Equal(a.epochReward.String(), g.AleutianEpochRewardStr)
r.Equal(a.numDelegatesForEpochReward, g.NumDelegatesForEpochReward)
r.Equal(a.foundationBonus.String(), g.FoundationBonusStr)
r.Equal(a.numDelegatesForFoundationBonus, g.NumDelegatesForFoundationBonus)
r.Equal(a.foundationBonusLastEpoch, g.FoundationBonusLastEpoch)
r.EqualValues(85, a.productivityThreshold)
r.False(a.hasFoundationBonusExtension())

// add foundation bonus extension
a.foundationBonusExtension = config.Default.Genesis.Rewarding.FoundationBonusExtension
b1, err := a.Serialize()
r.NoError(err)
r.Equal(b, b1[:len(b)])
a1 := admin{}
r.NoError(a1.Deserialize(b1))
r.True(a1.hasFoundationBonusExtension())
r.Equal(a, a1)
}

func TestProtocol_SetEpochReward(t *testing.T) {
testProtocol(t, func(t *testing.T, ctx context.Context, sm protocol.StateManager, p *Protocol) {
amount, err := p.EpochReward(ctx, sm)
Expand Down
83 changes: 71 additions & 12 deletions action/protocol/rewarding/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,55 @@ var (
epochRewardHistoryKeyPrefix = []byte("erh")
accountKeyPrefix = []byte("acc")
exemptKey = []byte("xpt")
errInvalidEpoch = errors.New("invalid start/end epoch number")
)

// Protocol defines the protocol of the rewarding fund and the rewarding process. It allows the admin to config the
// reward amount, users to donate tokens to the fund, block producers to grant them block and epoch reward and,
// beneficiaries to claim the balance into their personal account.
type Protocol struct {
keyPrefix []byte
addr address.Address
foundationBonusP2StartEpoch uint64
foundationBonusP2EndEpoch uint64
keyPrefix []byte
addr address.Address
cfg genesis.Rewarding
}

// NewProtocol instantiates a rewarding protocol instance.
func NewProtocol(
foundationBonusP2Start uint64,
foundationBonusP2End uint64,
) *Protocol {
func NewProtocol(cfg genesis.Rewarding) *Protocol {
h := hash.Hash160b([]byte(protocolID))
addr, err := address.FromBytes(h[:])
if err != nil {
log.L().Panic("Error when constructing the address of rewarding protocol", zap.Error(err))
}
if err = validateFoundationBonusExtension(cfg); err != nil {
log.L().Panic("failed to validate foundation bonus extension", zap.Error(err))
}
return &Protocol{
keyPrefix: h[:],
addr: addr,
foundationBonusP2StartEpoch: foundationBonusP2Start,
foundationBonusP2EndEpoch: foundationBonusP2End,
keyPrefix: h[:],
addr: addr,
cfg: cfg,
}
}

// verify that foundation bonus extension epochs are in increasing order
func validateFoundationBonusExtension(cfg genesis.Rewarding) error {
end := cfg.FoundationBonusLastEpoch
hasP2Extension := (cfg.FoundationBonusP2StartEpoch > 0 || cfg.FoundationBonusP2EndEpoch > 0)
if hasP2Extension {
if cfg.FoundationBonusP2StartEpoch < end || cfg.FoundationBonusP2EndEpoch < cfg.FoundationBonusP2StartEpoch {
return errInvalidEpoch
}
end = cfg.FoundationBonusP2EndEpoch
}

for _, v := range cfg.FoundationBonusExtension {
if v.Start < end || v.End < v.Start {
return errInvalidEpoch
}
end = v.End
}
return nil
}

// FindProtocol finds the registered protocol from registry
func FindProtocol(registry *protocol.Registry) *Protocol {
if registry == nil {
Expand Down Expand Up @@ -96,6 +115,8 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager
return p.SetReward(ctx, sm, g.DardanellesBlockReward(), true)
case g.GreenlandBlockHeight:
return p.migrateValueGreenland(ctx, sm)
case g.KamchatkaBlockHeight:
return p.setFoundationBonusExtension(ctx, sm)
}
return nil
}
Expand Down Expand Up @@ -124,6 +145,44 @@ func (p *Protocol) migrateValue(sm protocol.StateManager, key []byte, value inte
return p.deleteStateV1(sm, key)
}

func (p *Protocol) setFoundationBonusExtension(ctx context.Context, sm protocol.StateManager) error {
a := admin{}
if _, err := p.state(ctx, sm, adminKey, &a); err != nil {
return err
}

hasP2Extension := (p.cfg.FoundationBonusP2StartEpoch > 0 || p.cfg.FoundationBonusP2EndEpoch > 0)
if !a.hasFoundationBonusExtension() {
if hasP2Extension {
a.foundationBonusExtension = append(a.foundationBonusExtension,
genesis.Period{p.cfg.FoundationBonusP2StartEpoch, p.cfg.FoundationBonusP2EndEpoch})
}
for _, v := range p.cfg.FoundationBonusExtension {
a.foundationBonusExtension = append(a.foundationBonusExtension,
genesis.Period{v.Start, v.End})
}
return p.putState(ctx, sm, adminKey, &a)
}

// add the last/new foundation bonus extension period to admin
adminExtension := len(a.foundationBonusExtension)
configExtension := len(p.cfg.FoundationBonusExtension)
if hasP2Extension {
configExtension++
}
if configExtension != adminExtension+1 {
return errInvalidEpoch
}

last := len(p.cfg.FoundationBonusExtension) - 1
if p.cfg.FoundationBonusExtension[last].Start < a.foundationBonusExtension[adminExtension-1].End {
return errInvalidEpoch
}
a.foundationBonusExtension = append(a.foundationBonusExtension,
genesis.Period{p.cfg.FoundationBonusExtension[last].Start, p.cfg.FoundationBonusExtension[last].End})
return p.putState(ctx, sm, adminKey, &a)
}

// CreatePostSystemActions creates a list of system actions to be appended to block actions
func (p *Protocol) CreatePostSystemActions(ctx context.Context, _ protocol.StateReader) ([]action.Envelope, error) {
blkCtx := protocol.MustGetBlockCtx(ctx)
Expand Down
Loading

0 comments on commit ebe895c

Please sign in to comment.