mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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
	 jmahess
						jmahess