mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
[R4R] Pricefeed simulations (#420)
Co-authored-by: rhuairahrighairigh <ruaridh.odonnell@gmail.com> Co-authored-by: John Maheswaran <john@kava.io> Co-authored-by: Kevin Davis <kjydavis3@gmail.com>
This commit is contained in:
parent
3da4657102
commit
8d199746cd
@ -34,7 +34,9 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
|
||||
bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations"
|
||||
pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations"
|
||||
)
|
||||
|
||||
// Simulation parameter constants
|
||||
@ -57,6 +59,7 @@ const (
|
||||
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
|
||||
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
|
||||
OpWeightMsgUnjail = "op_weight_msg_unjail"
|
||||
OpWeightMsgPricefeed = "op_weight_msg_pricefeed"
|
||||
OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap"
|
||||
)
|
||||
|
||||
@ -277,6 +280,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper
|
||||
}(nil),
|
||||
bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper),
|
||||
},
|
||||
{
|
||||
func(_ *rand.Rand) int {
|
||||
var v int
|
||||
ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil,
|
||||
func(_ *rand.Rand) {
|
||||
v = 10000 // TODO
|
||||
})
|
||||
return v
|
||||
}(nil),
|
||||
pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +178,7 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice)
|
||||
// GetCurrentPrice fetches the current median price of all oracles for a specific market
|
||||
func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) {
|
||||
store := ctx.KVStore(k.key)
|
||||
|
||||
bz := store.Get([]byte(types.CurrentPricePrefix + marketID))
|
||||
|
||||
if bz == nil {
|
||||
@ -185,6 +186,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current
|
||||
}
|
||||
var price types.CurrentPrice
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &price)
|
||||
|
||||
if price.Price.Equal(sdk.ZeroDec()) {
|
||||
return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace)
|
||||
}
|
||||
|
@ -2,21 +2,64 @@ package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||
pricefeed "github.com/kava-labs/kava/x/pricefeed/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// RandomizedGenState generates a random GenesisState for pricefeed
|
||||
func RandomizedGenState(simState *module.SimulationState) {
|
||||
|
||||
// TODO implement this fully
|
||||
// - randomly generating the genesis params
|
||||
// - overwriting with genesis provided to simulation
|
||||
pricefeedGenesis := types.DefaultGenesisState()
|
||||
// get the params with xrp, btc and bnb to usd
|
||||
// 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))
|
||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis)
|
||||
}
|
||||
|
||||
// getPricefeedSimulationParams returns the params with xrp:usd, btc:usd, bnb:usd
|
||||
func getPricefeedSimulationParams() types.Params {
|
||||
// SET UP THE PRICEFEED GENESIS STATE
|
||||
pricefeedGenesis := pricefeed.GenesisState{
|
||||
Params: pricefeed.Params{
|
||||
Markets: []pricefeed.Market{
|
||||
pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
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},
|
||||
},
|
||||
},
|
||||
}
|
||||
return pricefeedGenesis.Params
|
||||
}
|
||||
|
||||
// getInitialPrice gets the starting price for each of the base assets
|
||||
func getInitialPrice(marketId string) (price sdk.Dec) {
|
||||
switch marketId {
|
||||
case "btc:usd":
|
||||
return sdk.MustNewDecFromStr("7000")
|
||||
case "bnb:usd":
|
||||
return sdk.MustNewDecFromStr("14")
|
||||
case "xrp:usd":
|
||||
return sdk.MustNewDecFromStr("0.2")
|
||||
}
|
||||
panic(fmt.Sprintf("Invalid marketId in getInitialPrice: %s\n", marketId))
|
||||
}
|
||||
|
136
x/pricefeed/simulation/operations/msg.go
Normal file
136
x/pricefeed/simulation/operations/msg.go
Normal file
@ -0,0 +1,136 @@
|
||||
package operations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
"github.com/kava-labs/kava/x/pricefeed/keeper"
|
||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
||||
var (
|
||||
noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName)
|
||||
)
|
||||
|
||||
// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price
|
||||
func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation {
|
||||
// get a pricefeed handler
|
||||
handler := pricefeed.NewHandler(keeper)
|
||||
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (
|
||||
simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
|
||||
// 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)
|
||||
|
||||
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)
|
||||
|
||||
// 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
|
||||
expiry := getExpiryTime(ctx)
|
||||
|
||||
// now create the msg to post price
|
||||
msg := types.NewMsgPostPrice(address, marketID, price, expiry)
|
||||
|
||||
// Perform basic validation of the msg - don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
}
|
||||
|
||||
// now we submit the pricefeed update message
|
||||
if ok := submitMsg(ctx, handler, msg); !ok {
|
||||
return noOpMsg, nil, fmt.Errorf("could not submit pricefeed msg")
|
||||
}
|
||||
return simulation.NewOperationMsg(msg, true, "pricefeed update submitted"), nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getRandomOracle picks a random oracle from the list of oracles
|
||||
func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress {
|
||||
randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles))
|
||||
oracle := market.Oracles[randomIndex]
|
||||
return oracle
|
||||
}
|
||||
|
||||
// pickRandomAsset picks a random asset out of the assets with equal probability
|
||||
// it returns the Market which includes the base asset as one of its fields
|
||||
func pickRandomAsset(ctx sdk.Context, keeper keeper.Keeper, r *rand.Rand) (market types.Market) {
|
||||
// get the params
|
||||
params := keeper.GetParams(ctx)
|
||||
// now pick a random asset
|
||||
randomIndex := simulation.RandIntBetween(r, 0, len(params.Markets))
|
||||
market = params.Markets[randomIndex]
|
||||
return market
|
||||
}
|
||||
|
||||
// getExpiryTime gets a price expiry time by taking the current time and adding a delta to it
|
||||
func getExpiryTime(ctx sdk.Context) (t time.Time) {
|
||||
// need to use the blocktime from the context as the context generates random start time when running simulations
|
||||
t = ctx.BlockTime().Add(time.Second * 1000000)
|
||||
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
|
||||
func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) {
|
||||
ctx, write := ctx.CacheContext()
|
||||
got := handler(ctx, msg)
|
||||
|
||||
ok = got.IsOK()
|
||||
if ok {
|
||||
write()
|
||||
}
|
||||
return ok
|
||||
}
|
Loading…
Reference in New Issue
Block a user