mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
[R4R] Fix pricefeed sims so does not always go to zero (#434)
* Generate pricefeed prices using a random walk at the beginning of sims Co-authored-by: rhuairahrighairigh <ruaridh.odonnell@gmail.com> Co-authored-by: John Maheswaran <john@kava.io> Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com> Co-authored-by: John Maheswaran <john@noreply> Co-authored-by: Kevin Davis <karzak@users.noreply.github.com> Co-authored-by: Kevin Davis <kjydavis3@gmail.com> Co-authored-by: Denali Marsh <denali@kava.io>
This commit is contained in:
parent
5ae0b76e0c
commit
55747ed0b8
@ -304,7 +304,7 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper
|
|||||||
})
|
})
|
||||||
return v
|
return v
|
||||||
}(nil),
|
}(nil),
|
||||||
pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper),
|
pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper, config.NumBlocks),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func(_ *rand.Rand) int {
|
func(_ *rand.Rand) int {
|
||||||
|
@ -156,7 +156,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s
|
|||||||
|
|
||||||
// repay debt 25% of the time
|
// repay debt 25% of the time
|
||||||
if hasCoins(acc, randDebtParam.Denom) {
|
if hasCoins(acc, randDebtParam.Denom) {
|
||||||
debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom)
|
debt := existingCDP.Principal.AmountOf(randDebtParam.Denom)
|
||||||
maxRepay := acc.GetCoins().AmountOf(randDebtParam.Denom)
|
maxRepay := acc.GetCoins().AmountOf(randDebtParam.Denom)
|
||||||
payableDebt := debt.Sub(randDebtParam.DebtFloor)
|
payableDebt := debt.Sub(randDebtParam.DebtFloor)
|
||||||
if maxRepay.GT(payableDebt) {
|
if maxRepay.GT(payableDebt) {
|
||||||
|
@ -12,51 +12,61 @@ import (
|
|||||||
pricefeed "github.com/kava-labs/kava/x/pricefeed/types"
|
pricefeed "github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BaseAssets is a list of collateral asset denoms
|
||||||
|
BaseAssets = [3]string{"bnb", "xrp", "btc"}
|
||||||
|
QuoteAsset = "usd"
|
||||||
|
)
|
||||||
|
|
||||||
// RandomizedGenState generates a random GenesisState for pricefeed
|
// RandomizedGenState generates a random GenesisState for pricefeed
|
||||||
func RandomizedGenState(simState *module.SimulationState) {
|
func RandomizedGenState(simState *module.SimulationState) {
|
||||||
// get the params with xrp, btc and bnb to usd
|
pricefeedGenesis := loadPricefeedGenState(simState)
|
||||||
// getPricefeedSimulationParams is defined to return params with xrp:usd, btc:usd, bnb:usd
|
|
||||||
params := getPricefeedSimulationParams()
|
|
||||||
markets := []types.Market{}
|
|
||||||
genPrices := []types.PostedPrice{}
|
|
||||||
// chose one account to be the oracle
|
|
||||||
oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))]
|
|
||||||
for _, market := range params.Markets {
|
|
||||||
updatedMarket := types.Market{market.MarketID, market.BaseAsset, market.QuoteAsset, []sdk.AccAddress{oracle.Address}, true}
|
|
||||||
markets = append(markets, updatedMarket)
|
|
||||||
genPrice := types.PostedPrice{market.MarketID, oracle.Address, getInitialPrice(market.MarketID), simState.GenTimestamp.Add(time.Hour * 24)}
|
|
||||||
genPrices = append(genPrices, genPrice)
|
|
||||||
}
|
|
||||||
params = types.NewParams(markets)
|
|
||||||
pricefeedGenesis := types.NewGenesisState(params, genPrices)
|
|
||||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis))
|
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis))
|
||||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis)
|
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPricefeedSimulationParams returns the params with xrp:usd, btc:usd, bnb:usd
|
// loadPricefeedGenState loads a valid pricefeed gen state
|
||||||
func getPricefeedSimulationParams() types.Params {
|
func loadPricefeedGenState(simState *module.SimulationState) pricefeed.GenesisState {
|
||||||
// SET UP THE PRICEFEED GENESIS STATE
|
var markets []pricefeed.Market
|
||||||
pricefeedGenesis := pricefeed.GenesisState{
|
var postedPrices []pricefeed.PostedPrice
|
||||||
Params: pricefeed.Params{
|
for _, denom := range BaseAssets {
|
||||||
Markets: []pricefeed.Market{
|
// Select an account to be the oracle
|
||||||
pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))]
|
||||||
pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
|
||||||
pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
marketID := fmt.Sprintf("%s:%s", denom, QuoteAsset)
|
||||||
},
|
// Construct market for asset
|
||||||
},
|
market := pricefeed.Market{
|
||||||
|
MarketID: marketID,
|
||||||
|
BaseAsset: denom,
|
||||||
|
QuoteAsset: QuoteAsset,
|
||||||
|
Oracles: []sdk.AccAddress{oracle.Address},
|
||||||
|
Active: true,
|
||||||
}
|
}
|
||||||
return pricefeedGenesis.Params
|
|
||||||
|
// Construct posted price for asset
|
||||||
|
postedPrice := pricefeed.PostedPrice{
|
||||||
|
MarketID: market.MarketID,
|
||||||
|
OracleAddress: oracle.Address,
|
||||||
|
Price: getInitialPrice(marketID),
|
||||||
|
Expiry: simState.GenTimestamp.Add(time.Hour * 24),
|
||||||
|
}
|
||||||
|
markets = append(markets, market)
|
||||||
|
postedPrices = append(postedPrices, postedPrice)
|
||||||
|
}
|
||||||
|
params := pricefeed.NewParams(markets)
|
||||||
|
return pricefeed.NewGenesisState(params, postedPrices)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInitialPrice gets the starting price for each of the base assets
|
// getInitialPrice gets the starting price for each of the base assets
|
||||||
func getInitialPrice(marketId string) (price sdk.Dec) {
|
func getInitialPrice(marketID string) (price sdk.Dec) {
|
||||||
switch marketId {
|
switch marketID {
|
||||||
case "btc:usd":
|
case "btc:usd":
|
||||||
return sdk.MustNewDecFromStr("7000")
|
return sdk.MustNewDecFromStr("7000")
|
||||||
case "bnb:usd":
|
case "bnb:usd":
|
||||||
return sdk.MustNewDecFromStr("14")
|
return sdk.MustNewDecFromStr("14")
|
||||||
case "xrp:usd":
|
case "xrp:usd":
|
||||||
return sdk.MustNewDecFromStr("0.2")
|
return sdk.MustNewDecFromStr("0.2")
|
||||||
|
default:
|
||||||
|
return sdk.MustNewDecFromStr("20") // Catch future additional assets
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("Invalid marketId in getInitialPrice: %s\n", marketId))
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package operations
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
@ -16,43 +17,53 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName)
|
noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName)
|
||||||
|
btcPrices = []sdk.Dec{}
|
||||||
|
bnbPrices = []sdk.Dec{}
|
||||||
|
xrpPrices = []sdk.Dec{}
|
||||||
|
genPrices sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price
|
// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price
|
||||||
func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation {
|
func SimulateMsgUpdatePrices(keeper keeper.Keeper, blocks int) simulation.Operation {
|
||||||
// get a pricefeed handler
|
// get a pricefeed handler
|
||||||
handler := pricefeed.NewHandler(keeper)
|
handler := pricefeed.NewHandler(keeper)
|
||||||
|
|
||||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||||
simulation.OperationMsg, []simulation.FutureOperation, error) {
|
simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||||
|
genPrices.Do(func() {
|
||||||
|
// generate a random walk for each asset exactly once, with observations equal to the number of blocks in the sim
|
||||||
|
for _, m := range keeper.GetMarkets(ctx) {
|
||||||
|
startPrice := getStartPrice(m.MarketID)
|
||||||
|
// allow prices to fluctuate from 10x GAINZ to 100x REKT
|
||||||
|
maxPrice := sdk.MustNewDecFromStr("10.0").Mul(startPrice)
|
||||||
|
minPrice := sdk.MustNewDecFromStr("0.01").Mul(startPrice)
|
||||||
|
previousPrice := startPrice
|
||||||
|
for i := 0; i < blocks; i++ {
|
||||||
|
increment := getIncrement(m.MarketID)
|
||||||
|
// note calling r instead of rand here breaks determinism
|
||||||
|
upDown := rand.Intn(2)
|
||||||
|
if upDown == 0 {
|
||||||
|
if previousPrice.Add(increment).GT(maxPrice) {
|
||||||
|
previousPrice = maxPrice
|
||||||
|
} else {
|
||||||
|
previousPrice = previousPrice.Add(increment)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if previousPrice.Sub(increment).LT(minPrice) {
|
||||||
|
previousPrice = minPrice
|
||||||
|
} else {
|
||||||
|
previousPrice = previousPrice.Sub(increment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPrice(m.MarketID, previousPrice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// OVERALL LOGIC:
|
|
||||||
// (1) RANDOMLY PICK AN ASSET OUT OF BNB AN BTC [TODO QUESTION - USDX IS EXCLUDED AS IT IS A STABLE DENOM
|
|
||||||
// (2) GET THE CURRENT PRICE OF THAT ASSET IN USD
|
|
||||||
// (3) GENERATE A RANDOM NUMBER IN THE RANGE 0.8-1.2 (UNIFORM DISTRIBUTION)
|
|
||||||
// (4) MULTIPLY THE CURRENT PRICE BY THE RANDOM NUMBER
|
|
||||||
// (5) POST THE NEW PRICE TO THE KEEPER
|
|
||||||
|
|
||||||
// pick a random asset out of BNB and BTC
|
|
||||||
randomMarket := pickRandomAsset(ctx, keeper, r)
|
randomMarket := pickRandomAsset(ctx, keeper, r)
|
||||||
|
|
||||||
marketID := randomMarket.MarketID
|
marketID := randomMarket.MarketID
|
||||||
|
|
||||||
// Get the current price of the asset
|
|
||||||
currentPrice, err := keeper.GetCurrentPrice(ctx, marketID) // Note this is marketID AND **NOT** just the base asset
|
|
||||||
if err != nil {
|
|
||||||
return noOpMsg, nil, fmt.Errorf("Error getting current price")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the address for the account
|
|
||||||
// this address needs to be an oracle and also exist. genesis should add all the accounts as oracles.
|
|
||||||
address := getRandomOracle(r, randomMarket)
|
address := getRandomOracle(r, randomMarket)
|
||||||
|
price := pickNewRandomPrice(marketID, int(ctx.BlockHeight()))
|
||||||
// generate a new random price based off the current price
|
|
||||||
price, err := pickNewRandomPrice(r, currentPrice.Price)
|
|
||||||
if err != nil {
|
|
||||||
return noOpMsg, nil, fmt.Errorf("Error picking random price")
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the expiry time based off the current time
|
// get the expiry time based off the current time
|
||||||
expiry := getExpiryTime(ctx)
|
expiry := getExpiryTime(ctx)
|
||||||
@ -73,6 +84,51 @@ func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStartPrice(marketID string) (startPrice sdk.Dec) {
|
||||||
|
switch marketID {
|
||||||
|
case "btc:usd":
|
||||||
|
return sdk.MustNewDecFromStr("7000")
|
||||||
|
case "bnb:usd":
|
||||||
|
return sdk.MustNewDecFromStr("15")
|
||||||
|
case "xrp:usd":
|
||||||
|
return sdk.MustNewDecFromStr("0.25")
|
||||||
|
}
|
||||||
|
return sdk.MustNewDecFromStr("100")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIncrement(marketID string) (increment sdk.Dec) {
|
||||||
|
startPrice := getStartPrice(marketID)
|
||||||
|
divisor := sdk.MustNewDecFromStr("20")
|
||||||
|
increment = startPrice.Quo(divisor)
|
||||||
|
return increment
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPrice(marketID string, price sdk.Dec) {
|
||||||
|
switch marketID {
|
||||||
|
case "btc:usd":
|
||||||
|
btcPrices = append(btcPrices, price)
|
||||||
|
return
|
||||||
|
case "bnb:usd":
|
||||||
|
bnbPrices = append(bnbPrices, price)
|
||||||
|
return
|
||||||
|
case "xrp:usd":
|
||||||
|
xrpPrices = append(xrpPrices, price)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickNewRandomPrice(marketID string, blockHeight int) (newPrice sdk.Dec) {
|
||||||
|
switch marketID {
|
||||||
|
case "btc:usd":
|
||||||
|
return btcPrices[blockHeight-1]
|
||||||
|
case "bnb:usd":
|
||||||
|
return bnbPrices[blockHeight-1]
|
||||||
|
case "xrp:usd":
|
||||||
|
return xrpPrices[blockHeight-1]
|
||||||
|
}
|
||||||
|
panic("invalid price request")
|
||||||
|
}
|
||||||
|
|
||||||
// getRandomOracle picks a random oracle from the list of oracles
|
// getRandomOracle picks a random oracle from the list of oracles
|
||||||
func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress {
|
func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress {
|
||||||
randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles))
|
randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles))
|
||||||
@ -98,31 +154,6 @@ func getExpiryTime(ctx sdk.Context) (t time.Time) {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// pickNewRandomPrice picks a new random price given the current price
|
|
||||||
// It takes the current price then generates a random number to multiply it by to create variation while
|
|
||||||
// still being in the similar range. Random walk style.
|
|
||||||
func pickNewRandomPrice(r *rand.Rand, currentPrice sdk.Dec) (price sdk.Dec, err sdk.Error) {
|
|
||||||
// Pick random price
|
|
||||||
// this is in the range [0-0.4) because when added to 0.8 it gives a multiplier in the range 0.8-1.2
|
|
||||||
got := sdk.MustNewDecFromStr("0.4")
|
|
||||||
|
|
||||||
randomPriceMultiplier := simulation.RandomDecAmount(r, got) // get a random number
|
|
||||||
if err != nil {
|
|
||||||
fmt.Errorf("Error generating random price multiplier\n")
|
|
||||||
return sdk.ZeroDec(), err
|
|
||||||
}
|
|
||||||
// 0.8 offset corresponds to 80% of the the current price
|
|
||||||
offset := sdk.MustNewDecFromStr("0.8")
|
|
||||||
|
|
||||||
// gives a result in range 0.8-1.2 inclusive, so the price can fluctuate from 80% to 120% of its current value
|
|
||||||
randomPriceMultiplier = randomPriceMultiplier.Add(offset)
|
|
||||||
|
|
||||||
// multiply the current price by the price multiplier
|
|
||||||
price = randomPriceMultiplier.Mul(currentPrice)
|
|
||||||
// return the price
|
|
||||||
return price, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not
|
// submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not
|
||||||
func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) {
|
func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) {
|
||||||
ctx, write := ctx.CacheContext()
|
ctx, write := ctx.CacheContext()
|
||||||
|
Loading…
Reference in New Issue
Block a user