mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
Incentive module simulations (#439)
* Incentive module simulations (#439) Co-authored-by: John Maheswaran <jmaheswaran@users.noreply.github.com> Co-authored-by: Kevin Davis <karzak@users.noreply.github.com> Co-authored-by: Kevin Davis <kjydavis3@gmail.com> Co-authored-by: John Maheswaran <john@kava.io>
This commit is contained in:
parent
e9a73b80ce
commit
b969a0ea33
@ -333,7 +333,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
|
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
|
||||||
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
||||||
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
||||||
incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper),
|
incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper),
|
||||||
)
|
)
|
||||||
|
|
||||||
// During begin block slashing happens after distr.BeginBlocker so that
|
// During begin block slashing happens after distr.BeginBlocker so that
|
||||||
@ -376,7 +376,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
|
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
|
||||||
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
|
||||||
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
|
||||||
incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper),
|
incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper),
|
||||||
)
|
)
|
||||||
|
|
||||||
app.sm.RegisterStoreDecoders()
|
app.sm.RegisterStoreDecoders()
|
||||||
|
@ -12,4 +12,5 @@ const (
|
|||||||
DefaultWeightMsgCreateAtomicSwap int = 100
|
DefaultWeightMsgCreateAtomicSwap int = 100
|
||||||
DefaultWeightMsgUpdatePrices int = 100
|
DefaultWeightMsgUpdatePrices int = 100
|
||||||
DefaultWeightMsgCdp int = 100
|
DefaultWeightMsgCdp int = 100
|
||||||
|
DefaultWeightMsgClaimReward int = 100
|
||||||
)
|
)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
for i in {1..10}; do
|
|
||||||
go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed ${i} -v -timeout 24h
|
|
||||||
done
|
|
@ -32,6 +32,7 @@ func RandomizedGenState(simState *module.SimulationState) {
|
|||||||
sdk.NewCoin("xrp", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))),
|
sdk.NewCoin("xrp", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))),
|
||||||
sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))),
|
sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))),
|
||||||
sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))),
|
sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))),
|
||||||
|
sdk.NewCoin("ukava", sdk.NewInt(int64(simState.Rand.Intn(500000000000)))),
|
||||||
)
|
)
|
||||||
err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd...))
|
err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
sim "github.com/cosmos/cosmos-sdk/x/simulation"
|
sim "github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/kava-labs/kava/x/incentive/client/cli"
|
"github.com/kava-labs/kava/x/incentive/client/cli"
|
||||||
@ -90,23 +91,25 @@ func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WeightedOperations returns the all the bep3 module operations with their respective weights.
|
// WeightedOperations returns the all the bep3 module operations with their respective weights.
|
||||||
func (am AppModule) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation {
|
func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
|
||||||
return nil
|
return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.supplyKeeper, am.keeper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppModule implements the sdk.AppModule interface.
|
// AppModule implements the sdk.AppModule interface.
|
||||||
type AppModule struct {
|
type AppModule struct {
|
||||||
AppModuleBasic
|
AppModuleBasic
|
||||||
|
|
||||||
keeper keeper.Keeper
|
keeper Keeper
|
||||||
supplyKeeper types.SupplyKeeper
|
accountKeeper auth.AccountKeeper
|
||||||
|
supplyKeeper SupplyKeeper
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppModule creates a new AppModule object
|
// NewAppModule creates a new AppModule object
|
||||||
func NewAppModule(keeper keeper.Keeper, supplyKeeper types.SupplyKeeper) AppModule {
|
func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule {
|
||||||
return AppModule{
|
return AppModule{
|
||||||
AppModuleBasic: AppModuleBasic{},
|
AppModuleBasic: AppModuleBasic{},
|
||||||
keeper: keeper,
|
keeper: keeper,
|
||||||
|
accountKeeper: accountKeeper,
|
||||||
supplyKeeper: supplyKeeper,
|
supplyKeeper: supplyKeeper,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,50 @@
|
|||||||
package simulation
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/tendermint/tendermint/libs/kv"
|
"github.com/tendermint/tendermint/libs/kv"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecodeStore unmarshals the KVPair's Value to the corresponding incentive type
|
// DecodeStore unmarshals the KVPair's Value to the module's corresponding type
|
||||||
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||||
// TODO implement this
|
switch {
|
||||||
return ""
|
case bytes.Equal(kvA.Key[:1], types.RewardPeriodKeyPrefix):
|
||||||
|
var rewardPeriodA, rewardPeriodB types.RewardPeriod
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &rewardPeriodA)
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &rewardPeriodB)
|
||||||
|
return fmt.Sprintf("%v\n%v", rewardPeriodA, rewardPeriodB)
|
||||||
|
|
||||||
|
case bytes.Equal(kvA.Key[:1], types.ClaimPeriodKeyPrefix):
|
||||||
|
var claimPeriodA, claimPeriodB types.ClaimPeriod
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimPeriodA)
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimPeriodB)
|
||||||
|
return fmt.Sprintf("%v\n%v", claimPeriodA, claimPeriodB)
|
||||||
|
|
||||||
|
case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix):
|
||||||
|
var claimA, claimB types.Claim
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimA)
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimB)
|
||||||
|
return fmt.Sprintf("%v\n%v", claimA, claimB)
|
||||||
|
|
||||||
|
case bytes.Equal(kvA.Key[:1], types.NextClaimPeriodIDPrefix):
|
||||||
|
claimPeriodIDA := binary.BigEndian.Uint64(kvA.Value)
|
||||||
|
claimPeriodIDB := binary.BigEndian.Uint64(kvB.Value)
|
||||||
|
return fmt.Sprintf("%d\n%d", claimPeriodIDA, claimPeriodIDB)
|
||||||
|
|
||||||
|
case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey):
|
||||||
|
var timeA, timeB time.Time
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
|
||||||
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)
|
||||||
|
return fmt.Sprintf("%s\n%s", timeA, timeB)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
65
x/incentive/simulation/decoder_test.go
Normal file
65
x/incentive/simulation/decoder_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tendermint/tendermint/libs/kv"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeTestCodec() (cdc *codec.Codec) {
|
||||||
|
cdc = codec.New()
|
||||||
|
sdk.RegisterCodec(cdc)
|
||||||
|
types.RegisterCodec(cdc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeDistributionStore(t *testing.T) {
|
||||||
|
cdc := makeTestCodec()
|
||||||
|
|
||||||
|
// Set up RewardPeriod, ClaimPeriod, Claim, and previous block time
|
||||||
|
rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(),
|
||||||
|
sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), time.Duration(time.Hour*2))
|
||||||
|
claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), time.Duration(time.Hour*24))
|
||||||
|
addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw")
|
||||||
|
claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1)
|
||||||
|
prevBlockTime := time.Now().Add(time.Hour * -1).UTC()
|
||||||
|
|
||||||
|
kvPairs := kv.Pairs{
|
||||||
|
kv.Pair{Key: types.RewardPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&rewardPeriod)},
|
||||||
|
kv.Pair{Key: types.ClaimPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claimPeriod)},
|
||||||
|
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claim)},
|
||||||
|
kv.Pair{Key: types.NextClaimPeriodIDPrefix, Value: sdk.Uint64ToBigEndian(10)},
|
||||||
|
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)},
|
||||||
|
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expectedLog string
|
||||||
|
}{
|
||||||
|
{"RewardPeriod", fmt.Sprintf("%v\n%v", rewardPeriod, rewardPeriod)},
|
||||||
|
{"ClaimPeriod", fmt.Sprintf("%v\n%v", claimPeriod, claimPeriod)},
|
||||||
|
{"Claim", fmt.Sprintf("%v\n%v", claim, claim)},
|
||||||
|
{"NextClaimPeriodID", "10\n10"},
|
||||||
|
{"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
|
||||||
|
{"other", ""},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
i, tt := i, tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
switch i {
|
||||||
|
case len(tests) - 1:
|
||||||
|
require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name)
|
||||||
|
default:
|
||||||
|
require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
140
x/incentive/simulation/genesis.go
Normal file
140
x/incentive/simulation/genesis.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
|
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CollateralDenoms = [3]string{"bnb", "xrp", "btc"}
|
||||||
|
RewardDenom = "ukava"
|
||||||
|
MaxTotalAssetReward = sdk.NewInt(1000000000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomizedGenState generates a random GenesisState for incentive module
|
||||||
|
func RandomizedGenState(simState *module.SimulationState) {
|
||||||
|
params := genParams(simState.Rand)
|
||||||
|
rewardPeriods := genRewardPeriods(simState.Rand, simState.GenTimestamp, params.Rewards)
|
||||||
|
claimPeriods := genClaimPeriods(rewardPeriods)
|
||||||
|
claimPeriodIDs := genNextClaimPeriodIds(claimPeriods)
|
||||||
|
|
||||||
|
// New genesis state holds valid, linked reward periods, claim periods, and claim period IDs
|
||||||
|
incentiveGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime,
|
||||||
|
rewardPeriods, claimPeriods, types.Claims{}, claimPeriodIDs)
|
||||||
|
if err := incentiveGenesis.Validate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, incentiveGenesis))
|
||||||
|
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(incentiveGenesis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// genParams generates random rewards and is active by default
|
||||||
|
func genParams(r *rand.Rand) types.Params {
|
||||||
|
params := types.NewParams(true, genRewards(r))
|
||||||
|
if err := params.Validate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// genRewards generates rewards for each specified collateral type
|
||||||
|
func genRewards(r *rand.Rand) types.Rewards {
|
||||||
|
var rewards types.Rewards
|
||||||
|
for _, denom := range CollateralDenoms {
|
||||||
|
active := true
|
||||||
|
// total reward is in range (half max total reward, max total reward)
|
||||||
|
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
|
||||||
|
totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount))
|
||||||
|
// generate a random number of hours between 6-48 to use for reward's times
|
||||||
|
numbHours := simulation.RandIntBetween(r, 6, 48)
|
||||||
|
duration := time.Duration(time.Hour * time.Duration(numbHours))
|
||||||
|
timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration
|
||||||
|
claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration
|
||||||
|
reward := types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration)
|
||||||
|
rewards = append(rewards, reward)
|
||||||
|
}
|
||||||
|
return rewards
|
||||||
|
}
|
||||||
|
|
||||||
|
// genRewardPeriods generates chronological reward periods for each given reward type
|
||||||
|
func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods {
|
||||||
|
var rewardPeriods types.RewardPeriods
|
||||||
|
for _, reward := range rewards {
|
||||||
|
rewardPeriodStart := timestamp
|
||||||
|
for i := 10; i >= simulation.RandIntBetween(r, 2, 9); i-- {
|
||||||
|
// Set up reward period parameters
|
||||||
|
start := rewardPeriodStart
|
||||||
|
end := start.Add(reward.Duration).UTC()
|
||||||
|
baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward
|
||||||
|
// Earlier periods have larger rewards
|
||||||
|
amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i))))
|
||||||
|
claimEnd := end.Add(reward.ClaimDuration)
|
||||||
|
claimTimeLock := reward.TimeLock
|
||||||
|
// Create reward period and append to array
|
||||||
|
rewardPeriod := types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock)
|
||||||
|
rewardPeriods = append(rewardPeriods, rewardPeriod)
|
||||||
|
// Update start time of next reward period
|
||||||
|
rewardPeriodStart = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rewardPeriods
|
||||||
|
}
|
||||||
|
|
||||||
|
// genClaimPeriods loads valid claim periods for an array of reward periods
|
||||||
|
func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||||
|
denomRewardPeriodsCount := make(map[string]uint64)
|
||||||
|
var claimPeriods types.ClaimPeriods
|
||||||
|
for _, rewardPeriod := range rewardPeriods {
|
||||||
|
// Increment reward period count for this denom (this is our claim period's ID)
|
||||||
|
denom := rewardPeriod.Denom
|
||||||
|
numbRewardPeriods := denomRewardPeriodsCount[denom] + 1
|
||||||
|
denomRewardPeriodsCount[denom] = numbRewardPeriods
|
||||||
|
// Set end and timelock from the associated reward period
|
||||||
|
end := rewardPeriod.ClaimEnd
|
||||||
|
claimTimeLock := rewardPeriod.ClaimTimeLock
|
||||||
|
// Create the new claim period for this reward period
|
||||||
|
claimPeriod := types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock)
|
||||||
|
claimPeriods = append(claimPeriods, claimPeriod)
|
||||||
|
}
|
||||||
|
return claimPeriods
|
||||||
|
}
|
||||||
|
|
||||||
|
// genNextClaimPeriodIds returns an array of the most recent claim period IDs for each denom
|
||||||
|
func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs {
|
||||||
|
// Build a map of the most recent claim periods by denom
|
||||||
|
mostRecentClaimPeriodByDenom := make(map[string]uint64)
|
||||||
|
for _, cp := range cps {
|
||||||
|
if cp.ID > mostRecentClaimPeriodByDenom[cp.Denom] {
|
||||||
|
mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write map contents to an array of GenesisClaimPeriodIDs
|
||||||
|
var claimPeriodIDs types.GenesisClaimPeriodIDs
|
||||||
|
for key, value := range mostRecentClaimPeriodByDenom {
|
||||||
|
claimPeriodID := types.GenesisClaimPeriodID{Denom: key, ID: value}
|
||||||
|
claimPeriodIDs = append(claimPeriodIDs, claimPeriodID)
|
||||||
|
}
|
||||||
|
return claimPeriodIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a list of accounts, replace the first account found with the same address. If not found, append the account.
|
||||||
|
func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount {
|
||||||
|
newAccounts := accounts
|
||||||
|
for i, a := range accounts {
|
||||||
|
if a.GetAddress().Equals(acc.GetAddress()) {
|
||||||
|
newAccounts[i] = acc
|
||||||
|
return newAccounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(newAccounts, acc)
|
||||||
|
}
|
146
x/incentive/simulation/operations.go
Normal file
146
x/incentive/simulation/operations.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
|
||||||
|
appparams "github.com/kava-labs/kava/app/params"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
|
"github.com/kava-labs/kava/x/kavadist"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simulation operation weights constants
|
||||||
|
const (
|
||||||
|
OpWeightMsgClaimReward = "op_weight_msg_claim_reward"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WeightedOperations returns all the operations from the module with their respective weights
|
||||||
|
func WeightedOperations(
|
||||||
|
appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper,
|
||||||
|
) simulation.WeightedOperations {
|
||||||
|
var weightMsgClaimReward int
|
||||||
|
|
||||||
|
appParams.GetOrGenerate(cdc, OpWeightMsgClaimReward, &weightMsgClaimReward, nil,
|
||||||
|
func(_ *rand.Rand) {
|
||||||
|
weightMsgClaimReward = appparams.DefaultWeightMsgClaimReward
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return simulation.WeightedOperations{
|
||||||
|
simulation.NewWeightedOperation(
|
||||||
|
weightMsgClaimReward,
|
||||||
|
SimulateMsgClaimReward(ak, sk, k),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateMsgClaimReward generates a MsgClaimReward
|
||||||
|
func SimulateMsgClaimReward(ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper) simulation.Operation {
|
||||||
|
return func(
|
||||||
|
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||||
|
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||||
|
|
||||||
|
// Load only account types that can claim rewards
|
||||||
|
var accounts []authexported.Account
|
||||||
|
validAccounts := make(map[string]bool)
|
||||||
|
for _, acc := range accs {
|
||||||
|
account := ak.GetAccount(ctx, acc.Address)
|
||||||
|
switch account.(type) {
|
||||||
|
case *vesting.PeriodicVestingAccount, *auth.BaseAccount: // Valid: BaseAccount, PeriodicVestingAccount
|
||||||
|
accounts = append(accounts, account)
|
||||||
|
validAccounts[account.GetAddress().String()] = true
|
||||||
|
break
|
||||||
|
default: // Invalid: ValidatorVestingAccount, DelayedVestingAccount, ContinuousVestingAccount
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load open claims and shuffle them to randomize
|
||||||
|
openClaims := types.Claims{}
|
||||||
|
k.IterateClaims(ctx, func(claim types.Claim) bool {
|
||||||
|
openClaims = append(openClaims, claim)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
r.Shuffle(len(openClaims), func(i, j int) {
|
||||||
|
openClaims[i], openClaims[j] = openClaims[j], openClaims[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
kavadistMacc := sk.GetModuleAccount(ctx, kavadist.KavaDistMacc)
|
||||||
|
kavadistBalance := kavadistMacc.SpendableCoins(ctx.BlockTime())
|
||||||
|
|
||||||
|
// Find address that has a claim of the same reward denom, then confirm it's distributable
|
||||||
|
claimer, claim, found := findValidAccountClaimPair(accs, openClaims, func(acc simulation.Account, claim types.Claim) bool {
|
||||||
|
if validAccounts[acc.Address.String()] { // Address must be valid type
|
||||||
|
if claim.Owner.Equals(acc.Address) { // Account must be claim owner
|
||||||
|
allClaims, found := k.GetClaimsByAddressAndDenom(ctx, claim.Owner, claim.Denom)
|
||||||
|
if found { // found should always be true
|
||||||
|
var rewards sdk.Coins
|
||||||
|
for _, individualClaim := range allClaims {
|
||||||
|
rewards = rewards.Add(individualClaim.Reward)
|
||||||
|
}
|
||||||
|
if rewards.AmountOf(claim.Reward.Denom).IsPositive() { // Can't distribute 0 coins
|
||||||
|
// Validate that kavadist module has enough coins to distribute rewards
|
||||||
|
if kavadistBalance.AmountOf(claim.Reward.Denom).GTE(rewards.AmountOf(claim.Reward.Denom)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if !found {
|
||||||
|
return simulation.NewOperationMsgBasic(types.ModuleName,
|
||||||
|
"no-operation (no accounts currently have fulfillable claims)", "", false, nil), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
claimerAcc := ak.GetAccount(ctx, claimer.Address)
|
||||||
|
if claimerAcc == nil {
|
||||||
|
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgClaimReward(claimer.Address, claim.Denom)
|
||||||
|
|
||||||
|
tx := helpers.GenTx(
|
||||||
|
[]sdk.Msg{msg},
|
||||||
|
sdk.NewCoins(),
|
||||||
|
helpers.DefaultGenTxGas,
|
||||||
|
chainID,
|
||||||
|
[]uint64{claimerAcc.GetAccountNumber()},
|
||||||
|
[]uint64{claimerAcc.GetSequence()},
|
||||||
|
claimer.PrivKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, result, err := app.Deliver(tx)
|
||||||
|
if err != nil {
|
||||||
|
// to aid debugging, add the stack trace to the comment field of the returned opMsg
|
||||||
|
return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// to aid debugging, add the result log to the comment field
|
||||||
|
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findValidAccountClaimPair finds an account and reward claim for which the callback func returns true
|
||||||
|
func findValidAccountClaimPair(accounts []simulation.Account, claims types.Claims,
|
||||||
|
cb func(simulation.Account, types.Claim) bool) (simulation.Account, types.Claim, bool) {
|
||||||
|
for _, claim := range claims {
|
||||||
|
for _, acc := range accounts {
|
||||||
|
if isValid := cb(acc, claim); isValid {
|
||||||
|
return acc, claim, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return simulation.Account{}, types.Claim{}, false
|
||||||
|
}
|
@ -1,14 +1,40 @@
|
|||||||
package simulation
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParamChanges defines the parameters that can be modified by param change proposals
|
const (
|
||||||
// on the simulation
|
keyActive = "Active"
|
||||||
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
keyRewards = "Rewards"
|
||||||
// TODO implement this
|
)
|
||||||
return []simulation.ParamChange{}
|
|
||||||
|
// genActive generates active bool with 80% chance of true
|
||||||
|
func genActive(r *rand.Rand) bool {
|
||||||
|
threshold := 80
|
||||||
|
value := simulation.RandIntBetween(r, 1, 100)
|
||||||
|
if value > threshold {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamChanges defines the parameters that can be modified by param change proposals
|
||||||
|
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
||||||
|
return []simulation.ParamChange{
|
||||||
|
simulation.NewSimParamChange(types.ModuleName, keyActive,
|
||||||
|
func(r *rand.Rand) string {
|
||||||
|
return fmt.Sprintf("\"%t\"", genActive(r))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
simulation.NewSimParamChange(types.ModuleName, keyRewards,
|
||||||
|
func(r *rand.Rand) string {
|
||||||
|
return fmt.Sprintf("\"%v\"", genRewards(r))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package simulation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RandomizedGenState generates a random GenesisState for cdp
|
|
||||||
func RandomizedGenState(simState *module.SimulationState) {
|
|
||||||
|
|
||||||
// TODO implement this fully
|
|
||||||
// - randomly generating the genesis params
|
|
||||||
// - overwriting with genesis provided to simulation
|
|
||||||
genesis := types.DefaultGenesisState()
|
|
||||||
|
|
||||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis))
|
|
||||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis)
|
|
||||||
}
|
|
@ -14,10 +14,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SecondsPerYear is the number of seconds in a year
|
// SecondsPerYear is the number of seconds in a year
|
||||||
const SecondsPerYear = 31536000
|
const (
|
||||||
|
SecondsPerYear = 31536000
|
||||||
// BaseAprPadding prevents the calculated SPR inflation rate from being 0.0
|
// BaseAprPadding sets the minimum inflation to the calculated SPR inflation rate from being 0.0
|
||||||
const BaseAprPadding = "0.000000000100000000"
|
BaseAprPadding = "0.000000003022265980"
|
||||||
|
)
|
||||||
|
|
||||||
// RandomizedGenState generates a random GenesisState for kavadist module
|
// RandomizedGenState generates a random GenesisState for kavadist module
|
||||||
func RandomizedGenState(simState *module.SimulationState) {
|
func RandomizedGenState(simState *module.SimulationState) {
|
||||||
@ -46,8 +47,8 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods {
|
|||||||
numPeriods := simulation.RandIntBetween(r, 1, 10)
|
numPeriods := simulation.RandIntBetween(r, 1, 10)
|
||||||
periodStart := timestamp
|
periodStart := timestamp
|
||||||
for i := 0; i < numPeriods; i++ {
|
for i := 0; i < numPeriods; i++ {
|
||||||
// set periods to be between 2 weeks and 2 years
|
// set periods to be between 1-3 days
|
||||||
durationMultiplier := simulation.RandIntBetween(r, 14, 104)
|
durationMultiplier := simulation.RandIntBetween(r, 1, 3)
|
||||||
duration := time.Duration(int64(24*durationMultiplier)) * time.Hour
|
duration := time.Duration(int64(24*durationMultiplier)) * time.Hour
|
||||||
periodEnd := periodStart.Add(duration)
|
periodEnd := periodStart.Add(duration)
|
||||||
inflation := genRandomInflation(r)
|
inflation := genRandomInflation(r)
|
||||||
@ -59,13 +60,14 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genRandomInflation(r *rand.Rand) sdk.Dec {
|
func genRandomInflation(r *rand.Rand) sdk.Dec {
|
||||||
// If sim.RandomDecAmount returns 0 (happens frequently by design), add BaseAprPadding
|
// If sim.RandomDecAmount is less than base apr padding, add base apr padding
|
||||||
|
aprPadding, _ := sdk.NewDecFromStr(BaseAprPadding)
|
||||||
extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25"))
|
extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25"))
|
||||||
for extraAprInflation.Equal(sdk.ZeroDec()) {
|
for extraAprInflation.LT(aprPadding) {
|
||||||
extraAprInflation = extraAprInflation.Add(sdk.MustNewDecFromStr(BaseAprPadding))
|
extraAprInflation = extraAprInflation.Add(aprPadding)
|
||||||
}
|
}
|
||||||
|
|
||||||
aprInflation := sdk.OneDec().Add(extraAprInflation)
|
aprInflation := sdk.OneDec().Add(extraAprInflation)
|
||||||
|
|
||||||
// convert APR inflation to SPR (inflation per second)
|
// convert APR inflation to SPR (inflation per second)
|
||||||
inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear))
|
inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user