[WIP] Kavadist Simulations (#435)

* feat: kavadist sims

* refactor genesis, add validation

* implement params

* rename simulation to genesis

Co-authored-by: Kevin Davis <kjydavis3@gmail.com>
This commit is contained in:
Denali Marsh 2020-04-14 14:42:08 -07:00 committed by GitHub
parent acc96952a7
commit 45e40fe357
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 209 additions and 24 deletions

View File

@ -0,0 +1,84 @@
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"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/kavadist/types"
)
// SecondsPerYear is the number of seconds in a year
const SecondsPerYear = 31536000
// BaseAprPadding prevents the calculated SPR inflation rate from being 0.0
const BaseAprPadding = "0.000000000100000000"
// RandomizedGenState generates a random GenesisState for kavadist module
func RandomizedGenState(simState *module.SimulationState) {
params := genRandomParams(simState)
if err := params.Validate(); err != nil {
panic(err)
}
kavadistGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime)
if err := kavadistGenesis.Validate(); err != nil {
panic(err)
}
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, kavadistGenesis))
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(kavadistGenesis)
}
func genRandomParams(simState *module.SimulationState) types.Params {
periods := genRandomPeriods(simState.Rand, simState.GenTimestamp)
params := types.NewParams(true, periods)
return params
}
func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods {
var periods types.Periods
numPeriods := simulation.RandIntBetween(r, 1, 10)
periodStart := timestamp
for i := 0; i < numPeriods; i++ {
// set periods to be between 2 weeks and 2 years
durationMultiplier := simulation.RandIntBetween(r, 14, 104)
duration := time.Duration(int64(24*durationMultiplier)) * time.Hour
periodEnd := periodStart.Add(duration)
inflation := genRandomInflation(r)
period := types.NewPeriod(periodStart, periodEnd, inflation)
periods = append(periods, period)
periodStart = periodEnd
}
return periods
}
func genRandomInflation(r *rand.Rand) sdk.Dec {
// If sim.RandomDecAmount returns 0 (happens frequently by design), add BaseAprPadding
extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25"))
for extraAprInflation.Equal(sdk.ZeroDec()) {
extraAprInflation = extraAprInflation.Add(sdk.MustNewDecFromStr(BaseAprPadding))
}
aprInflation := sdk.OneDec().Add(extraAprInflation)
// convert APR inflation to SPR (inflation per second)
inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear))
if err != nil {
panic(fmt.Sprintf("error generating random inflation %v", err))
}
return inflationSpr
}
func genRandomActive(r *rand.Rand) bool {
threshold := 50
value := simulation.RandIntBetween(r, 1, 100)
if value > threshold {
return true
}
return false
}

View File

@ -1,14 +1,34 @@
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/kavadist/types"
) )
// ParamChanges defines the parameters that can be modified by param change proposals // ParamChanges defines the parameters that can be modified by param change proposals
// on the simulation // on the simulation
func ParamChanges(r *rand.Rand) []simulation.ParamChange { func ParamChanges(r *rand.Rand) []simulation.ParamChange {
// TODO implement this // Hacky way to validate periods since validation is wrapped in params
return []simulation.ParamChange{} active := genRandomActive(r)
periods := genRandomPeriods(r, simulation.RandTimestamp(r))
if err := types.NewParams(active, periods).Validate(); err != nil {
panic(err)
}
return []simulation.ParamChange{
simulation.NewSimParamChange(types.ModuleName, string(types.KeyActive), "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%t\"", active)
},
),
simulation.NewSimParamChange(types.ModuleName, string(types.KeyPeriods), "",
func(r *rand.Rand) string {
return fmt.Sprintf("\"%v\"", periods)
},
),
}
} }

View File

@ -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/kavadist/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)
}

View File

@ -0,0 +1,64 @@
package simulation
import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// TODO use the official version available in v0.38.2 of the sdk
// https://github.com/cosmos/cosmos-sdk/blob/e8d89a2fe26175b73545a3e79ae783032b4e975e/types/decimal.go#L328
func approxRoot(d sdk.Dec, root uint64) (guess sdk.Dec, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = errors.New("out of bounds")
}
}
}()
if d.IsNegative() {
absRoot, err := approxRoot(d.MulInt64(-1), root)
return absRoot.MulInt64(-1), err
}
if root == 1 || d.IsZero() || d.Equal(sdk.OneDec()) {
return d, nil
}
if root == 0 {
return sdk.OneDec(), nil
}
rootInt := sdk.NewInt(int64(root))
guess, delta := sdk.OneDec(), sdk.OneDec()
for delta.Abs().GT(sdk.SmallestDec()) {
prev := power(guess, (root - 1))
if prev.IsZero() {
prev = sdk.SmallestDec()
}
delta = d.Quo(prev)
delta = delta.Sub(guess)
delta = delta.QuoInt(rootInt)
guess = guess.Add(delta)
}
return guess, nil
}
// Power returns a the result of raising to a positive integer power
func power(d sdk.Dec, power uint64) sdk.Dec {
if power == 0 {
return sdk.OneDec()
}
tmp := sdk.OneDec()
for i := power; i > 1; {
if i%2 == 0 {
i /= 2
} else {
tmp = tmp.Mul(d)
i = (i - 1) / 2
}
d = d.Mul(d)
}
return d.Mul(tmp)
}

View File

@ -0,0 +1,30 @@
package simulation
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestApproxRoot(t *testing.T) {
testCases := []struct {
input sdk.Dec
root uint64
expected sdk.Dec
}{
{sdk.OneDec(), 10, sdk.OneDec()}, // 1.0 ^ (0.1) => 1.0
{sdk.NewDecWithPrec(25, 2), 2, sdk.NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5
{sdk.NewDecWithPrec(4, 2), 2, sdk.NewDecWithPrec(2, 1)}, // 0.04 => 0.2
{sdk.NewDecFromInt(sdk.NewInt(27)), 3, sdk.NewDecFromInt(sdk.NewInt(3))}, // 27 ^ (1/3) => 3
{sdk.NewDecFromInt(sdk.NewInt(-81)), 4, sdk.NewDecFromInt(sdk.NewInt(-3))}, // -81 ^ (0.25) => -3
{sdk.NewDecFromInt(sdk.NewInt(2)), 2, sdk.NewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049
{sdk.NewDecWithPrec(1005, 3), 31536000, sdk.MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) = 1.000000000158153904
}
for i, tc := range testCases {
res, err := approxRoot(tc.input, tc.root)
require.NoError(t, err)
require.True(t, tc.expected.Sub(res).Abs().LTE(sdk.SmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input)
}
}

View File

@ -33,6 +33,15 @@ type Period struct {
Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation
} }
// NewPeriod returns a new instance of Period
func NewPeriod(start time.Time, end time.Time, inflation sdk.Dec) Period {
return Period{
Start: start,
End: end,
Inflation: inflation,
}
}
// String implements fmt.Stringer // String implements fmt.Stringer
func (pr Period) String() string { func (pr Period) String() string {
return fmt.Sprintf(`Period: return fmt.Sprintf(`Period: