mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 18:27:26 +00:00 
			
		
		
		
	Merge branch 'master' into kd-pricefeed-cleanup
This commit is contained in:
		
						commit
						f425efc56c
					
				
							
								
								
									
										13
									
								
								app/app.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								app/app.go
									
									
									
									
									
								
							| @ -302,16 +302,15 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, | |||||||
| 
 | 
 | ||||||
| 	app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) | 	app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) | ||||||
| 
 | 
 | ||||||
| 	// Note: genutils must occur after staking so that pools are properly
 |  | ||||||
| 	// initialized with tokens from genesis accounts.
 |  | ||||||
| 	//
 |  | ||||||
| 	// Note: Changing the order of the auth module and modules that use module accounts
 |  | ||||||
| 	// results in subtle changes to the way accounts are loaded from genesis.
 |  | ||||||
| 	app.mm.SetOrderInitGenesis( | 	app.mm.SetOrderInitGenesis( | ||||||
| 		auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName, | 		auth.ModuleName, // loads all accounts - should run before any module with a module account
 | ||||||
|  | 		validatorvesting.ModuleName, distr.ModuleName, | ||||||
| 		staking.ModuleName, bank.ModuleName, slashing.ModuleName, | 		staking.ModuleName, bank.ModuleName, slashing.ModuleName, | ||||||
| 		gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, | 		gov.ModuleName, mint.ModuleName, | ||||||
| 		pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok?
 | 		pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok?
 | ||||||
|  | 		supply.ModuleName,  // calculates the total supply from account - should run after modules that modify accounts in genesis
 | ||||||
|  | 		crisis.ModuleName,  // runs the invariants at genesis - should run after other modules
 | ||||||
|  | 		genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts.
 | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	app.mm.RegisterInvariants(&app.crisisKeeper) | 	app.mm.RegisterInvariants(&app.crisisKeeper) | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ import ( | |||||||
| 	stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" | 	stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" | ||||||
| 	"github.com/cosmos/cosmos-sdk/x/supply" | 	"github.com/cosmos/cosmos-sdk/x/supply" | ||||||
| 
 | 
 | ||||||
|  | 	auctionsimops "github.com/kava-labs/kava/x/auction/simulation/operations" | ||||||
| 	bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" | 	bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" | ||||||
| 	pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" | 	pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" | ||||||
| ) | ) | ||||||
| @ -59,6 +60,7 @@ const ( | |||||||
| 	OpWeightMsgUndelegate                              = "op_weight_msg_undelegate" | 	OpWeightMsgUndelegate                              = "op_weight_msg_undelegate" | ||||||
| 	OpWeightMsgBeginRedelegate                         = "op_weight_msg_begin_redelegate" | 	OpWeightMsgBeginRedelegate                         = "op_weight_msg_begin_redelegate" | ||||||
| 	OpWeightMsgUnjail                                  = "op_weight_msg_unjail" | 	OpWeightMsgUnjail                                  = "op_weight_msg_unjail" | ||||||
|  | 	OpWeightMsgPlaceBid                                = "op_weight_msg_place_bid" | ||||||
| 	OpWeightMsgPricefeed                               = "op_weight_msg_pricefeed" | 	OpWeightMsgPricefeed                               = "op_weight_msg_pricefeed" | ||||||
| 	OpWeightMsgCreateAtomicSwap                        = "op_weight_msg_create_atomic_Swap" | 	OpWeightMsgCreateAtomicSwap                        = "op_weight_msg_create_atomic_Swap" | ||||||
| ) | ) | ||||||
| @ -269,6 +271,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper | |||||||
| 			}(nil), | 			}(nil), | ||||||
| 			slashingsimops.SimulateMsgUnjail(app.slashingKeeper), | 			slashingsimops.SimulateMsgUnjail(app.slashingKeeper), | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			func(_ *rand.Rand) int { | ||||||
|  | 				var v int | ||||||
|  | 				ap.GetOrGenerate(app.cdc, OpWeightMsgPlaceBid, &v, nil, | ||||||
|  | 					func(_ *rand.Rand) { | ||||||
|  | 						v = 100 | ||||||
|  | 					}) | ||||||
|  | 				return v | ||||||
|  | 			}(nil), | ||||||
|  | 			auctionsimops.SimulateMsgPlaceBid(app.accountKeeper, app.auctionKeeper), | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			func(_ *rand.Rand) int { | 			func(_ *rand.Rand) int { | ||||||
| 				var v int | 				var v int | ||||||
| @ -285,7 +298,7 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper | |||||||
| 				var v int | 				var v int | ||||||
| 				ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, | 				ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, | ||||||
| 					func(_ *rand.Rand) { | 					func(_ *rand.Rand) { | ||||||
| 						v = 10000 // TODO
 | 						v = 100 | ||||||
| 					}) | 					}) | ||||||
| 				return v | 				return v | ||||||
| 			}(nil), | 			}(nil), | ||||||
|  | |||||||
| @ -55,6 +55,7 @@ var ( | |||||||
| 	// functions aliases
 | 	// functions aliases
 | ||||||
| 	NewKeeper                            = keeper.NewKeeper | 	NewKeeper                            = keeper.NewKeeper | ||||||
| 	NewQuerier                           = keeper.NewQuerier | 	NewQuerier                           = keeper.NewQuerier | ||||||
|  | 	RegisterInvariants                   = keeper.RegisterInvariants | ||||||
| 	NewSurplusAuction                    = types.NewSurplusAuction | 	NewSurplusAuction                    = types.NewSurplusAuction | ||||||
| 	NewDebtAuction                       = types.NewDebtAuction | 	NewDebtAuction                       = types.NewDebtAuction | ||||||
| 	NewCollateralAuction                 = types.NewCollateralAuction | 	NewCollateralAuction                 = types.NewCollateralAuction | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper | |||||||
| 	for _, a := range gs.Auctions { | 	for _, a := range gs.Auctions { | ||||||
| 		keeper.SetAuction(ctx, a) | 		keeper.SetAuction(ctx, a) | ||||||
| 		// find the total coins that should be present in the module account
 | 		// find the total coins that should be present in the module account
 | ||||||
| 		totalAuctionCoins.Add(a.GetModuleAccountCoins()) | 		totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check if the module account exists
 | 	// check if the module account exists
 | ||||||
|  | |||||||
| @ -29,6 +29,12 @@ func TestInitGenesis(t *testing.T) { | |||||||
| 		tApp := app.NewTestApp() | 		tApp := app.NewTestApp() | ||||||
| 		keeper := tApp.GetAuctionKeeper() | 		keeper := tApp.GetAuctionKeeper() | ||||||
| 		ctx := tApp.NewContext(true, abci.Header{}) | 		ctx := tApp.NewContext(true, abci.Header{}) | ||||||
|  | 		// setup module account
 | ||||||
|  | 		supplyKeeper := tApp.GetSupplyKeeper() | ||||||
|  | 		moduleAcc := supplyKeeper.GetModuleAccount(ctx, auction.ModuleName) | ||||||
|  | 		require.NoError(t, moduleAcc.SetCoins(testAuction.GetModuleAccountCoins())) | ||||||
|  | 		supplyKeeper.SetModuleAccount(ctx, moduleAcc) | ||||||
|  | 
 | ||||||
| 		// create genesis
 | 		// create genesis
 | ||||||
| 		gs := auction.NewGenesisState( | 		gs := auction.NewGenesisState( | ||||||
| 			10, | 			10, | ||||||
| @ -38,7 +44,7 @@ func TestInitGenesis(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| 		// run init
 | 		// run init
 | ||||||
| 		require.NotPanics(t, func() { | 		require.NotPanics(t, func() { | ||||||
| 			auction.InitGenesis(ctx, keeper, tApp.GetSupplyKeeper(), gs) | 			auction.InitGenesis(ctx, keeper, supplyKeeper, gs) | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		// check state is as expected
 | 		// check state is as expected
 | ||||||
| @ -59,7 +65,7 @@ func TestInitGenesis(t *testing.T) { | |||||||
| 			return false | 			return false | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("invalid", func(t *testing.T) { | 	t.Run("invalid (invalid nextAuctionID)", func(t *testing.T) { | ||||||
| 		// setup keepers
 | 		// setup keepers
 | ||||||
| 		tApp := app.NewTestApp() | 		tApp := app.NewTestApp() | ||||||
| 		ctx := tApp.NewContext(true, abci.Header{}) | 		ctx := tApp.NewContext(true, abci.Header{}) | ||||||
| @ -71,6 +77,24 @@ func TestInitGenesis(t *testing.T) { | |||||||
| 			auction.GenesisAuctions{testAuction}, | 			auction.GenesisAuctions{testAuction}, | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
|  | 		// check init fails
 | ||||||
|  | 		require.Panics(t, func() { | ||||||
|  | 			auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid (missing mod account coins)", func(t *testing.T) { | ||||||
|  | 		// setup keepers
 | ||||||
|  | 		tApp := app.NewTestApp() | ||||||
|  | 		ctx := tApp.NewContext(true, abci.Header{}) | ||||||
|  | 
 | ||||||
|  | 		// create invalid genesis
 | ||||||
|  | 		gs := auction.NewGenesisState( | ||||||
|  | 			10, | ||||||
|  | 			auction.DefaultParams(), | ||||||
|  | 			auction.GenesisAuctions{testAuction}, | ||||||
|  | 		) | ||||||
|  | 		// invalid as there is no module account setup
 | ||||||
|  | 
 | ||||||
| 		// check init fails
 | 		// check init fails
 | ||||||
| 		require.Panics(t, func() { | 		require.Panics(t, func() { | ||||||
| 			auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs) | 			auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs) | ||||||
|  | |||||||
| @ -175,7 +175,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder | |||||||
| 		), | 		), | ||||||
| 	) | 	) | ||||||
| 	if bid.Amount.LT(minNewBidAmt) { | 	if bid.Amount.LT(minNewBidAmt) { | ||||||
| 		return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt)) | 		return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic
 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// New bidder pays back old bidder
 | 	// New bidder pays back old bidder
 | ||||||
| @ -239,7 +239,7 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc | |||||||
| 	) | 	) | ||||||
| 	minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
 | 	minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
 | ||||||
| 	if bid.Amount.LT(minNewBidAmt) { | 	if bid.Amount.LT(minNewBidAmt) { | ||||||
| 		return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt)) | 		return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic
 | ||||||
| 	} | 	} | ||||||
| 	if a.MaxBid.IsLT(bid) { | 	if a.MaxBid.IsLT(bid) { | ||||||
| 		return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid) | 		return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid) | ||||||
| @ -314,10 +314,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc | |||||||
| 		), | 		), | ||||||
| 	) | 	) | ||||||
| 	if lot.Amount.GT(maxNewLotAmt) { | 	if lot.Amount.GT(maxNewLotAmt) { | ||||||
| 		return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt)) | 		return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic
 | ||||||
| 	} | 	} | ||||||
| 	if lot.IsNegative() { | 	if lot.IsNegative() { | ||||||
| 		return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt())) | 		return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// New bidder pays back old bidder
 | 	// New bidder pays back old bidder
 | ||||||
| @ -380,10 +380,10 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac | |||||||
| 		), | 		), | ||||||
| 	) | 	) | ||||||
| 	if lot.Amount.GT(maxNewLotAmt) { | 	if lot.Amount.GT(maxNewLotAmt) { | ||||||
| 		return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt)) | 		return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic
 | ||||||
| 	} | 	} | ||||||
| 	if lot.IsNegative() { | 	if lot.IsNegative() { | ||||||
| 		return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt())) | 		return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// New bidder pays back old bidder
 | 	// New bidder pays back old bidder
 | ||||||
|  | |||||||
							
								
								
									
										143
									
								
								x/auction/keeper/invariants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								x/auction/keeper/invariants.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | |||||||
|  | package keeper | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cosmos/cosmos-sdk/store/prefix" | ||||||
|  | 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||||
|  | 
 | ||||||
|  | 	"github.com/kava-labs/kava/x/auction/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // RegisterInvariants registers all staking invariants
 | ||||||
|  | func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { | ||||||
|  | 
 | ||||||
|  | 	ir.RegisterRoute(types.ModuleName, "module-account", | ||||||
|  | 		ModuleAccountInvariants(k)) | ||||||
|  | 	ir.RegisterRoute(types.ModuleName, "valid-auctions", | ||||||
|  | 		ValidAuctionInvariant(k)) | ||||||
|  | 	ir.RegisterRoute(types.ModuleName, "valid-index", | ||||||
|  | 		ValidIndexInvariant(k)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ModuleAccountInvariant checks that the module account's coins matches those stored in auctions
 | ||||||
|  | func ModuleAccountInvariants(k Keeper) sdk.Invariant { | ||||||
|  | 	return func(ctx sdk.Context) (string, bool) { | ||||||
|  | 
 | ||||||
|  | 		totalAuctionCoins := sdk.NewCoins() | ||||||
|  | 		k.IterateAuctions(ctx, func(auction types.Auction) bool { | ||||||
|  | 			a, ok := auction.(types.GenesisAuction) | ||||||
|  | 			if !ok { | ||||||
|  | 				panic("stored auction type does not fulfill GenesisAuction interface") | ||||||
|  | 			} | ||||||
|  | 			totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) | ||||||
|  | 			return false | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		moduleAccCoins := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins() | ||||||
|  | 		broken := !moduleAccCoins.IsEqual(totalAuctionCoins) | ||||||
|  | 
 | ||||||
|  | 		invariantMessage := sdk.FormatInvariant( | ||||||
|  | 			types.ModuleName, | ||||||
|  | 			"module account", | ||||||
|  | 			fmt.Sprintf( | ||||||
|  | 				"\texpected ModuleAccount coins: %s\n"+ | ||||||
|  | 					"\tactual ModuleAccount coins:   %s\n", | ||||||
|  | 				totalAuctionCoins, moduleAccCoins), | ||||||
|  | 		) | ||||||
|  | 		return invariantMessage, broken | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ValidAuctionInvariant verifies that all auctions in the store are independently valid
 | ||||||
|  | func ValidAuctionInvariant(k Keeper) sdk.Invariant { | ||||||
|  | 	return func(ctx sdk.Context) (string, bool) { | ||||||
|  | 		var validationErr error | ||||||
|  | 		var invalidAuction types.Auction | ||||||
|  | 		k.IterateAuctions(ctx, func(auction types.Auction) bool { | ||||||
|  | 			a, ok := auction.(types.GenesisAuction) | ||||||
|  | 			if !ok { | ||||||
|  | 				panic("stored auction type does not fulfill GenesisAuction interface") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			currentTime := ctx.BlockTime() | ||||||
|  | 			if !currentTime.Equal(time.Time{}) { // this avoids a simulator bug where app.InitGenesis is called with blockTime=0 instead of the correct time
 | ||||||
|  | 				if a.GetEndTime().Before(currentTime) { | ||||||
|  | 					validationErr = fmt.Errorf("endTime after current block time (%s)", currentTime) | ||||||
|  | 					invalidAuction = a | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if err := a.Validate(); err != nil { | ||||||
|  | 				validationErr = err | ||||||
|  | 				invalidAuction = a | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			return false | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		broken := validationErr != nil | ||||||
|  | 		invariantMessage := sdk.FormatInvariant( | ||||||
|  | 			types.ModuleName, | ||||||
|  | 			"valid auctions", | ||||||
|  | 			fmt.Sprintf( | ||||||
|  | 				"\tfound invalid auction, reason: %s\n"+ | ||||||
|  | 					"\tauction:\n\t%s\n", | ||||||
|  | 				validationErr, invalidAuction), | ||||||
|  | 		) | ||||||
|  | 		return invariantMessage, broken | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ValidIndexInvariant checks that all auctions in the store are also in the index and vice versa.
 | ||||||
|  | func ValidIndexInvariant(k Keeper) sdk.Invariant { | ||||||
|  | 	return func(ctx sdk.Context) (string, bool) { | ||||||
|  | 		/* Method: | ||||||
|  | 		- check all the auction IDs in the index have a corresponding auction in the store | ||||||
|  | 		- index is now valid but there could be extra auction in the store | ||||||
|  | 		- check for these extra auctions by checking num items in the store equals that of index (store keys are always unique) | ||||||
|  | 		- doesn't check the IDs in the auction structs match the IDs in the keys | ||||||
|  | 		*/ | ||||||
|  | 
 | ||||||
|  | 		// Check all auction IDs in the index are in the auction store
 | ||||||
|  | 		store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) | ||||||
|  | 
 | ||||||
|  | 		indexIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) | ||||||
|  | 		defer indexIterator.Close() | ||||||
|  | 
 | ||||||
|  | 		var indexLength int | ||||||
|  | 		for ; indexIterator.Valid(); indexIterator.Next() { | ||||||
|  | 			indexLength++ | ||||||
|  | 
 | ||||||
|  | 			idBytes := indexIterator.Value() | ||||||
|  | 			auctionBytes := store.Get(idBytes) | ||||||
|  | 			if auctionBytes == nil { | ||||||
|  | 				invariantMessage := sdk.FormatInvariant( | ||||||
|  | 					types.ModuleName, | ||||||
|  | 					"valid index", | ||||||
|  | 					fmt.Sprintf("\tauction with ID '%d' found in index but not in store", types.Uint64FromBytes(idBytes))) | ||||||
|  | 				return invariantMessage, true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Check length of auction store matches the length of the index
 | ||||||
|  | 		storeIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) | ||||||
|  | 		defer storeIterator.Close() | ||||||
|  | 		var storeLength int | ||||||
|  | 		for ; storeIterator.Valid(); storeIterator.Next() { | ||||||
|  | 			storeLength++ | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if storeLength != indexLength { | ||||||
|  | 			invariantMessage := sdk.FormatInvariant( | ||||||
|  | 				types.ModuleName, | ||||||
|  | 				"valid index", | ||||||
|  | 				fmt.Sprintf("\tmismatched number of items in auction store (%d) and index (%d)", storeLength, indexLength)) | ||||||
|  | 			return invariantMessage, true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -110,8 +110,10 @@ func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RegisterInvariants performs a no-op.
 | // RegisterInvariants registers the module invariants.
 | ||||||
| func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} | func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { | ||||||
|  | 	RegisterInvariants(ir, am.keeper) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // Route module message route name
 | // Route module message route name
 | ||||||
| func (AppModule) Route() string { | func (AppModule) Route() string { | ||||||
|  | |||||||
| @ -2,21 +2,170 @@ package simulation | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"math/rand" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/cosmos/cosmos-sdk/codec" | 	"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/types/module" | ||||||
|  | 	"github.com/cosmos/cosmos-sdk/x/auth" | ||||||
|  | 	authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" | ||||||
|  | 	"github.com/cosmos/cosmos-sdk/x/simulation" | ||||||
|  | 	"github.com/cosmos/cosmos-sdk/x/supply" | ||||||
| 
 | 
 | ||||||
| 	"github.com/kava-labs/kava/x/auction/types" | 	"github.com/kava-labs/kava/x/auction/types" | ||||||
|  | 	cdptypes "github.com/kava-labs/kava/x/cdp/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | const ( | ||||||
|  | 	// Block time params are un-exported constants in cosmos-sdk/x/simulation.
 | ||||||
|  | 	// Copy them here in lieu of importing them.
 | ||||||
|  | 	minTimePerBlock time.Duration = (10000 / 2) * time.Second | ||||||
|  | 	maxTimePerBlock time.Duration = 10000 * time.Second | ||||||
|  | 
 | ||||||
|  | 	// Calculate the average block time
 | ||||||
|  | 	AverageBlockTime time.Duration = (maxTimePerBlock - minTimePerBlock) / 2 | ||||||
|  | 	// MaxBidDuration is a crude way of ensuring that BidDuration ≤ MaxAuctionDuration for all generated params
 | ||||||
|  | 	MaxBidDuration time.Duration = AverageBlockTime * 50 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func GenBidDuration(r *rand.Rand) time.Duration { | ||||||
|  | 	d, err := RandomPositiveDuration(r, 0, MaxBidDuration) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return d | ||||||
|  | } | ||||||
|  | func GenMaxAuctionDuration(r *rand.Rand) time.Duration { | ||||||
|  | 	d, err := RandomPositiveDuration(r, MaxBidDuration, AverageBlockTime*200) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return d | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GenIncrementCollateral(r *rand.Rand) sdk.Dec { | ||||||
|  | 	return simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("1")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var GenIncrementDebt = GenIncrementCollateral | ||||||
|  | var GenIncrementSurplus = GenIncrementCollateral | ||||||
|  | 
 | ||||||
| // RandomizedGenState generates a random GenesisState for auction
 | // RandomizedGenState generates a random GenesisState for auction
 | ||||||
| func RandomizedGenState(simState *module.SimulationState) { | func RandomizedGenState(simState *module.SimulationState) { | ||||||
| 
 | 
 | ||||||
| 	// TODO implement this fully
 | 	p := types.NewParams( | ||||||
| 	// - randomly generating the genesis params
 | 		GenMaxAuctionDuration(simState.Rand), | ||||||
| 	// - overwriting with genesis provided to simulation
 | 		GenBidDuration(simState.Rand), | ||||||
| 	auctionGenesis := types.DefaultGenesisState() | 		GenIncrementSurplus(simState.Rand), | ||||||
|  | 		GenIncrementDebt(simState.Rand), | ||||||
|  | 		GenIncrementCollateral(simState.Rand), | ||||||
|  | 	) | ||||||
|  | 	if err := p.Validate(); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	auctionGenesis := types.NewGenesisState( | ||||||
|  | 		types.DefaultNextAuctionID, | ||||||
|  | 		p, | ||||||
|  | 		nil, | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
|  | 	// Add auctions
 | ||||||
|  | 	auctions := types.GenesisAuctions{ | ||||||
|  | 		types.NewDebtAuction( | ||||||
|  | 			cdptypes.LiquidatorMacc, // using cdp account rather than generic test one to avoid having to set permissions on the supply keeper
 | ||||||
|  | 			sdk.NewInt64Coin("usdx", 100), | ||||||
|  | 			sdk.NewInt64Coin("ukava", 1000000000000), | ||||||
|  | 			simState.GenTimestamp.Add(time.Hour*5), | ||||||
|  | 			sdk.NewInt64Coin("debt", 100), // same as usdx
 | ||||||
|  | 		), | ||||||
|  | 	} | ||||||
|  | 	var startingID = auctionGenesis.NextAuctionID | ||||||
|  | 	var ok bool | ||||||
|  | 	var totalAuctionCoins sdk.Coins | ||||||
|  | 	for i, a := range auctions { | ||||||
|  | 		auctions[i], ok = a.WithID(uint64(i) + startingID).(types.GenesisAuction) | ||||||
|  | 		if !ok { | ||||||
|  | 			panic("can't convert Auction to GenesisAuction") | ||||||
|  | 		} | ||||||
|  | 		totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) | ||||||
|  | 	} | ||||||
|  | 	auctionGenesis.NextAuctionID = startingID + uint64(len(auctions)) | ||||||
|  | 	auctionGenesis.Auctions = append(auctionGenesis.Auctions, auctions...) | ||||||
|  | 
 | ||||||
|  | 	// Also need to update the auction module account (to reflect the coins held in the auctions)
 | ||||||
|  | 	var authGenesis auth.GenesisState | ||||||
|  | 	simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) | ||||||
|  | 
 | ||||||
|  | 	auctionModAcc, found := getAccount(authGenesis.Accounts, supply.NewModuleAddress(types.ModuleName)) | ||||||
|  | 	if !found { | ||||||
|  | 		auctionModAcc = supply.NewEmptyModuleAccount(types.ModuleName) | ||||||
|  | 	} | ||||||
|  | 	if err := auctionModAcc.SetCoins(totalAuctionCoins); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, auctionModAcc) | ||||||
|  | 
 | ||||||
|  | 	// TODO adding bidder coins as well - this should be moved elsewhere
 | ||||||
|  | 	bidder, found := getAccount(authGenesis.Accounts, simState.Accounts[0].Address) // 0 is the bidder // FIXME
 | ||||||
|  | 	if !found { | ||||||
|  | 		panic("bidder not found") | ||||||
|  | 	} | ||||||
|  | 	bidderCoins := sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000000)) | ||||||
|  | 	if err := bidder.SetCoins(bidder.GetCoins().Add(bidderCoins)); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, bidder) | ||||||
|  | 
 | ||||||
|  | 	simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) | ||||||
|  | 
 | ||||||
|  | 	// Update the supply genesis state to reflect the new coins
 | ||||||
|  | 	// TODO find some way for this to happen automatically / move it elsewhere
 | ||||||
|  | 	var supplyGenesis supply.GenesisState | ||||||
|  | 	simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) | ||||||
|  | 	supplyGenesis.Supply = supplyGenesis.Supply.Add(totalAuctionCoins).Add(bidderCoins) | ||||||
|  | 	simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) | ||||||
|  | 
 | ||||||
|  | 	// TODO liquidator mod account doesn't need to be initialized for this example
 | ||||||
|  | 	// - it just mints kava, doesn't need a starting balance
 | ||||||
|  | 	// - and supply.GetModuleAccount creates one if it doesn't exist
 | ||||||
|  | 
 | ||||||
|  | 	// Note: this line prints out the auction genesis state, not just the auction parameters. Some sdk modules print out just the parameters.
 | ||||||
| 	fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, auctionGenesis)) | 	fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, auctionGenesis)) | ||||||
| 	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(auctionGenesis) | 	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(auctionGenesis) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Return an account from a list of accounts that matches an address.
 | ||||||
|  | func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) { | ||||||
|  | 	for _, a := range accounts { | ||||||
|  | 		if a.GetAddress().Equals(addr) { | ||||||
|  | 			return a, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func RandomPositiveDuration(r *rand.Rand, inclusiveMin, exclusiveMax time.Duration) (time.Duration, error) { | ||||||
|  | 	min := int64(inclusiveMin) | ||||||
|  | 	max := int64(exclusiveMax) | ||||||
|  | 	if min < 0 || max < 0 { | ||||||
|  | 		return 0, fmt.Errorf("min and max must be positive") | ||||||
|  | 	} | ||||||
|  | 	if min >= max { | ||||||
|  | 		return 0, fmt.Errorf("max must be < min") | ||||||
|  | 	} | ||||||
|  | 	randPositiveInt64 := r.Int63n(max-min) + min | ||||||
|  | 	return time.Duration(randPositiveInt64), nil | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										177
									
								
								x/auction/simulation/operations/msg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								x/auction/simulation/operations/msg.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | |||||||
|  | package operations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"math/big" | ||||||
|  | 	"math/rand" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cosmos/cosmos-sdk/baseapp" | ||||||
|  | 	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/simulation" | ||||||
|  | 
 | ||||||
|  | 	"github.com/kava-labs/kava/x/auction" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	noOpMsg             = simulation.NoOpMsg(auction.ModuleName) | ||||||
|  | 	ErrorNotEnoughCoins = errors.New("account doesn't have enough coins") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Return a function that runs a random state change on the module keeper.
 | ||||||
|  | // There's two error paths
 | ||||||
|  | // - return a OpMessage, but nil error - this will log a message but keep running the simulation
 | ||||||
|  | // - return an error - this will stop the simulation
 | ||||||
|  | func SimulateMsgPlaceBid(authKeeper auth.AccountKeeper, keeper auction.Keeper) simulation.Operation { | ||||||
|  | 	handler := auction.NewHandler(keeper) | ||||||
|  | 
 | ||||||
|  | 	return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( | ||||||
|  | 		simulation.OperationMsg, []simulation.FutureOperation, error) { | ||||||
|  | 
 | ||||||
|  | 		// get open auctions
 | ||||||
|  | 		openAuctions := auction.Auctions{} | ||||||
|  | 		keeper.IterateAuctions(ctx, func(a auction.Auction) bool { | ||||||
|  | 			openAuctions = append(openAuctions, a) | ||||||
|  | 			return false | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		// shuffle auctions slice so that bids are evenly distributed across auctions
 | ||||||
|  | 		rand.Shuffle(len(openAuctions), func(i, j int) { | ||||||
|  | 			openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i] | ||||||
|  | 		}) | ||||||
|  | 		// TODO do the same for accounts?
 | ||||||
|  | 		var accounts []authexported.Account | ||||||
|  | 		for _, acc := range accs { | ||||||
|  | 			accounts = append(accounts, authKeeper.GetAccount(ctx, acc.Address)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction)
 | ||||||
|  | 		blockTime := ctx.BlockHeader().Time | ||||||
|  | 		bidder, openAuction, found := findValidAccountAuctionPair(accounts, openAuctions, func(acc authexported.Account, auc auction.Auction) bool { | ||||||
|  | 			_, err := generateBidAmount(r, auc, acc, blockTime) | ||||||
|  | 			if err == ErrorNotEnoughCoins { | ||||||
|  | 				return false // keep searching
 | ||||||
|  | 			} else if err != nil { | ||||||
|  | 				panic(err) // raise errors
 | ||||||
|  | 			} | ||||||
|  | 			return true // found valid pair
 | ||||||
|  | 		}) | ||||||
|  | 		if !found { | ||||||
|  | 			return simulation.NewOperationMsgBasic(auction.ModuleName, "no-operation (no valid auction and bidder)", "", false, nil), nil, nil | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// pick a bid amount for the chosen auction and bidder
 | ||||||
|  | 		amount, _ := generateBidAmount(r, openAuction, bidder, blockTime) | ||||||
|  | 
 | ||||||
|  | 		// create a msg
 | ||||||
|  | 		msg := auction.NewMsgPlaceBid(openAuction.GetID(), bidder.GetAddress(), amount) | ||||||
|  | 		if err := msg.ValidateBasic(); err != nil { // don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic
 | ||||||
|  | 			return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// submit the msg
 | ||||||
|  | 		result := submitMsg(ctx, handler, msg) | ||||||
|  | 		// Return an operationMsg indicating whether the msg was submitted successfully
 | ||||||
|  | 		// Using result.Log as the comment field as it contains any error message emitted by the keeper
 | ||||||
|  | 		return simulation.NewOperationMsg(msg, result.IsOK(), result.Log), nil, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) sdk.Result { | ||||||
|  | 	ctx, write := ctx.CacheContext() | ||||||
|  | 	result := handler(ctx, msg) | ||||||
|  | 	if result.IsOK() { | ||||||
|  | 		write() | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func generateBidAmount(r *rand.Rand, auc auction.Auction, bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { | ||||||
|  | 	bidderBalance := bidder.SpendableCoins(blockTime) | ||||||
|  | 
 | ||||||
|  | 	switch a := auc.(type) { | ||||||
|  | 
 | ||||||
|  | 	case auction.DebtAuction: | ||||||
|  | 		if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin
 | ||||||
|  | 			return sdk.Coin{}, ErrorNotEnoughCoins | ||||||
|  | 		} | ||||||
|  | 		amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount // TODO min bid increments
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 		return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin
 | ||||||
|  | 
 | ||||||
|  | 	case auction.SurplusAuction: | ||||||
|  | 		if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // gov coin // TODO account for bid increments
 | ||||||
|  | 			return sdk.Coin{}, ErrorNotEnoughCoins | ||||||
|  | 		} | ||||||
|  | 		amt, err := RandIntInclusive(r, a.Bid.Amount, bidderBalance.AmountOf(a.Bid.Denom)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 		return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin
 | ||||||
|  | 
 | ||||||
|  | 	case auction.CollateralAuction: | ||||||
|  | 		if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin // TODO account for bid increments (in forward phase)
 | ||||||
|  | 			return sdk.Coin{}, ErrorNotEnoughCoins | ||||||
|  | 		} | ||||||
|  | 		if a.IsReversePhase() { | ||||||
|  | 			amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount
 | ||||||
|  | 			if err != nil { | ||||||
|  | 				panic(err) | ||||||
|  | 			} | ||||||
|  | 			return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin
 | ||||||
|  | 		} else { | ||||||
|  | 			amt, err := RandIntInclusive(r, a.Bid.Amount, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				panic(err) | ||||||
|  | 			} | ||||||
|  | 			// pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase
 | ||||||
|  | 			if r.Intn(10) == 0 { // 10%
 | ||||||
|  | 				amt = a.MaxBid.Amount | ||||||
|  | 			} | ||||||
|  | 			return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		return sdk.Coin{}, fmt.Errorf("unknown auction type") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // findValidAccountAuctionPair finds an auction and account for which the callback func returns true
 | ||||||
|  | func findValidAccountAuctionPair(accounts []authexported.Account, auctions auction.Auctions, cb func(authexported.Account, auction.Auction) bool) (authexported.Account, auction.Auction, bool) { | ||||||
|  | 	for _, auc := range auctions { | ||||||
|  | 		for _, acc := range accounts { | ||||||
|  | 			if isValid := cb(acc, auc); isValid { | ||||||
|  | 				return acc, auc, true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, nil, false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RandInt randomly generates an sdk.Int in the range [inclusiveMin, inclusiveMax]. It works for negative and positive integers.
 | ||||||
|  | func RandIntInclusive(r *rand.Rand, inclusiveMin, inclusiveMax sdk.Int) (sdk.Int, error) { | ||||||
|  | 	if inclusiveMin.GT(inclusiveMax) { | ||||||
|  | 		return sdk.Int{}, fmt.Errorf("min larger than max") | ||||||
|  | 	} | ||||||
|  | 	return RandInt(r, inclusiveMin, inclusiveMax.Add(sdk.OneInt())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RandInt randomly generates an sdk.Int in the range [inclusiveMin, exclusiveMax). It works for negative and positive integers.
 | ||||||
|  | func RandInt(r *rand.Rand, inclusiveMin, exclusiveMax sdk.Int) (sdk.Int, error) { | ||||||
|  | 	// validate input
 | ||||||
|  | 	if inclusiveMin.GTE(exclusiveMax) { | ||||||
|  | 		return sdk.Int{}, fmt.Errorf("min larger or equal to max") | ||||||
|  | 	} | ||||||
|  | 	// shift the range to start at 0
 | ||||||
|  | 	shiftedRange := exclusiveMax.Sub(inclusiveMin) // should always be positive given the check above
 | ||||||
|  | 	// randomly pick from the shifted range
 | ||||||
|  | 	shiftedRandInt := sdk.NewIntFromBigInt(new(big.Int).Rand(r, shiftedRange.BigInt())) | ||||||
|  | 	// shift back to the original range
 | ||||||
|  | 	return shiftedRandInt.Add(inclusiveMin), nil | ||||||
|  | } | ||||||
| @ -1,14 +1,46 @@ | |||||||
| 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/auction/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
 | 	// Note: params are encoded to JSON before being stored in the param store. These param changes
 | ||||||
| 	return []simulation.ParamChange{} | 	// update the raw values in the store so values need to be JSON. This is why values that are represented
 | ||||||
|  | 	// as strings in JSON (such as time.Duration) have the escaped quotes.
 | ||||||
|  | 	// TODO should we encode the values properly with ModuleCdc.MustMarshalJSON()?
 | ||||||
|  | 	return []simulation.ParamChange{ | ||||||
|  | 		simulation.NewSimParamChange(types.ModuleName, string(types.KeyBidDuration), "", | ||||||
|  | 			func(r *rand.Rand) string { | ||||||
|  | 				return fmt.Sprintf("\"%d\"", GenBidDuration(r)) | ||||||
|  | 			}, | ||||||
|  | 		), | ||||||
|  | 		simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxAuctionDuration), "", | ||||||
|  | 			func(r *rand.Rand) string { | ||||||
|  | 				return fmt.Sprintf("\"%d\"", GenMaxAuctionDuration(r)) | ||||||
|  | 			}, | ||||||
|  | 		), | ||||||
|  | 		simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementCollateral), "", | ||||||
|  | 			func(r *rand.Rand) string { | ||||||
|  | 				return fmt.Sprintf("\"%d\"", GenIncrementCollateral(r)) | ||||||
|  | 			}, | ||||||
|  | 		), | ||||||
|  | 		simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementDebt), "", | ||||||
|  | 			func(r *rand.Rand) string { | ||||||
|  | 				return fmt.Sprintf("\"%d\"", GenIncrementDebt(r)) | ||||||
|  | 			}, | ||||||
|  | 		), | ||||||
|  | 		simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementSurplus), "", | ||||||
|  | 			func(r *rand.Rand) string { | ||||||
|  | 				return fmt.Sprintf("\"%d\"", GenIncrementSurplus(r)) | ||||||
|  | 			}, | ||||||
|  | 		), | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -69,6 +69,12 @@ func (a BaseAuction) Validate() error { | |||||||
| 	if a.EndTime.After(a.MaxEndTime) { | 	if a.EndTime.After(a.MaxEndTime) { | ||||||
| 		return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime) | 		return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime) | ||||||
| 	} | 	} | ||||||
|  | 	if !a.Lot.IsValid() { | ||||||
|  | 		return fmt.Errorf("invalid lot: %s", a.Lot) | ||||||
|  | 	} | ||||||
|  | 	if !a.Bid.IsValid() { | ||||||
|  | 		return fmt.Errorf("invalid bid: %s", a.Bid) | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -148,6 +154,13 @@ func (a DebtAuction) GetModuleAccountCoins() sdk.Coins { | |||||||
| // GetPhase returns the direction of a debt auction, which never changes.
 | // GetPhase returns the direction of a debt auction, which never changes.
 | ||||||
| func (a DebtAuction) GetPhase() string { return "reverse" } | func (a DebtAuction) GetPhase() string { return "reverse" } | ||||||
| 
 | 
 | ||||||
|  | func (a DebtAuction) Validate() error { | ||||||
|  | 	if !a.CorrespondingDebt.IsValid() { | ||||||
|  | 		return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) | ||||||
|  | 	} | ||||||
|  | 	return a.BaseAuction.Validate() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // NewDebtAuction returns a new debt auction.
 | // NewDebtAuction returns a new debt auction.
 | ||||||
| func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction { | func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction { | ||||||
| 	// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
 | 	// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
 | ||||||
| @ -208,6 +221,19 @@ func (a CollateralAuction) GetPhase() string { | |||||||
| 	return "forward" | 	return "forward" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (a CollateralAuction) Validate() error { | ||||||
|  | 	if !a.CorrespondingDebt.IsValid() { | ||||||
|  | 		return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) | ||||||
|  | 	} | ||||||
|  | 	if !a.MaxBid.IsValid() { | ||||||
|  | 		return fmt.Errorf("invalid max bid: %s", a.MaxBid) | ||||||
|  | 	} | ||||||
|  | 	if err := a.LotReturns.Validate(); err != nil { | ||||||
|  | 		return fmt.Errorf("invalid lot returns: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return a.BaseAuction.Validate() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (a CollateralAuction) String() string { | func (a CollateralAuction) String() string { | ||||||
| 	return fmt.Sprintf(`Auction %d: | 	return fmt.Sprintf(`Auction %d: | ||||||
|   Initiator:              %s |   Initiator:              %s | ||||||
| @ -251,16 +277,24 @@ type WeightedAddresses struct { | |||||||
| 
 | 
 | ||||||
| // NewWeightedAddresses returns a new list addresses with weights.
 | // NewWeightedAddresses returns a new list addresses with weights.
 | ||||||
| func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { | func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { | ||||||
| 	if len(addrs) != len(weights) { | 	wa := WeightedAddresses{ | ||||||
| 		return WeightedAddresses{}, sdk.ErrInternal("number of addresses doesn't match number of weights") |  | ||||||
| 	} |  | ||||||
| 	for _, w := range weights { |  | ||||||
| 		if w.IsNegative() { |  | ||||||
| 			return WeightedAddresses{}, sdk.ErrInternal("weights contain a negative amount") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return WeightedAddresses{ |  | ||||||
| 		Addresses: addrs, | 		Addresses: addrs, | ||||||
| 		Weights:   weights, | 		Weights:   weights, | ||||||
| 	}, nil | 	} | ||||||
|  | 	if err := wa.Validate(); err != nil { | ||||||
|  | 		return WeightedAddresses{}, sdk.ErrInternal(err.Error()) | ||||||
|  | 	} | ||||||
|  | 	return wa, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (wa WeightedAddresses) Validate() error { | ||||||
|  | 	if len(wa.Addresses) != len(wa.Weights) { | ||||||
|  | 		return fmt.Errorf("number of addresses doesn't match number of weights") | ||||||
|  | 	} | ||||||
|  | 	for _, w := range wa.Weights { | ||||||
|  | 		if w.IsNegative() { | ||||||
|  | 			return fmt.Errorf("weights contain a negative amount") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,7 +4,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"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" | ||||||
| @ -52,8 +51,8 @@ func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 { | |||||||
| // GenSupportedAssets gets randomized SupportedAssets
 | // GenSupportedAssets gets randomized SupportedAssets
 | ||||||
| func GenSupportedAssets(r *rand.Rand) types.AssetParams { | func GenSupportedAssets(r *rand.Rand) types.AssetParams { | ||||||
| 	var assets types.AssetParams | 	var assets types.AssetParams | ||||||
| 	for i := 0; i < (r.Intn(10) + 1); i++ { | 	numAssets := (r.Intn(10) + 1) | ||||||
| 		r := rand.New(rand.NewSource(time.Now().UnixNano())) | 	for i := 0; i < numAssets; i++ { | ||||||
| 		denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) | 		denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) | ||||||
| 		asset := genSupportedAsset(r, denom) | 		asset := genSupportedAsset(r, denom) | ||||||
| 		assets = append(assets, asset) | 		assets = append(assets, asset) | ||||||
|  | |||||||
| @ -33,13 +33,13 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat | |||||||
| 		senderOtherChain := simulation.RandStringOfLength(r, 43) | 		senderOtherChain := simulation.RandStringOfLength(r, 43) | ||||||
| 
 | 
 | ||||||
| 		// Generate cryptographically strong pseudo-random number
 | 		// Generate cryptographically strong pseudo-random number
 | ||||||
| 		randomNumber, err := types.GenerateSecureRandomNumber() | 		randomNumber, err := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return noOpMsg, nil, err | 			return noOpMsg, nil, err | ||||||
| 		} | 		} | ||||||
| 		// Must use current blocktime instead of 'now' since initial blocktime was randomly generated
 | 		// Must use current blocktime instead of 'now' since initial blocktime was randomly generated
 | ||||||
| 		timestamp := ctx.BlockTime().Unix() | 		timestamp := ctx.BlockTime().Unix() | ||||||
| 		randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) | 		randomNumberHash := types.CalculateRandomHash(randomNumber.BigInt().Bytes(), timestamp) | ||||||
| 
 | 
 | ||||||
| 		// Randomly select an asset from supported assets
 | 		// Randomly select an asset from supported assets
 | ||||||
| 		assets, found := k.GetAssets(ctx) | 		assets, found := k.GetAssets(ctx) | ||||||
| @ -90,7 +90,7 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat | |||||||
| 			if evenOdd%2 == 0 { | 			if evenOdd%2 == 0 { | ||||||
| 				// Claim future operation
 | 				// Claim future operation
 | ||||||
| 				executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) | 				executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) | ||||||
| 				futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler) | 				futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.BigInt().Bytes(), executionBlock, handler) | ||||||
| 			} else { | 			} else { | ||||||
| 				// Refund future operation
 | 				// Refund future operation
 | ||||||
| 				executionBlock := ctx.BlockHeight() + msg.HeightSpan | 				executionBlock := ctx.BlockHeight() + msg.HeightSpan | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Kevin Davis
						Kevin Davis