mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 14:47:29 +00:00 
			
		
		
		
	[R4R] BEP3 simulations (#423)
* implement randomized genesis, params * implement operations: MsgCreateAtomicSwap * implement claim, refund future ops * remove dynamic block locks * refactor BondedAddresses * add consistent supported assets
This commit is contained in:
		
							parent
							
								
									1bdf580a61
								
							
						
					
					
						commit
						3da4657102
					
				| @ -34,6 +34,7 @@ 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" | ||||
| ) | ||||
| 
 | ||||
| // Simulation parameter constants
 | ||||
| @ -56,6 +57,7 @@ const ( | ||||
| 	OpWeightMsgUndelegate                              = "op_weight_msg_undelegate" | ||||
| 	OpWeightMsgBeginRedelegate                         = "op_weight_msg_begin_redelegate" | ||||
| 	OpWeightMsgUnjail                                  = "op_weight_msg_unjail" | ||||
| 	OpWeightMsgCreateAtomicSwap                        = "op_weight_msg_create_atomic_Swap" | ||||
| ) | ||||
| 
 | ||||
| // TestMain runs setup and teardown code before all tests.
 | ||||
| @ -264,6 +266,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper | ||||
| 			}(nil), | ||||
| 			slashingsimops.SimulateMsgUnjail(app.slashingKeeper), | ||||
| 		}, | ||||
| 		{ | ||||
| 			func(_ *rand.Rand) int { | ||||
| 				var v int | ||||
| 				ap.GetOrGenerate(app.cdc, OpWeightMsgCreateAtomicSwap, &v, nil, | ||||
| 					func(_ *rand.Rand) { | ||||
| 						v = 100 | ||||
| 					}) | ||||
| 				return v | ||||
| 			}(nil), | ||||
| 			bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -2,21 +2,167 @@ package simulation | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/codec" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/types/module" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/x/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/bep3/types" | ||||
| ) | ||||
| 
 | ||||
| // Simulation parameter constants
 | ||||
| const ( | ||||
| 	BnbDeputyAddress = "bnb_deputy_address" | ||||
| 	MinBlockLock     = "min_block_lock" | ||||
| 	MaxBlockLock     = "max_block_lock" | ||||
| 	SupportedAssets  = "supported_assets" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	MaxSupplyLimit   = sdk.NewInt(10000000000000000) | ||||
| 	Accs             []simulation.Account | ||||
| 	ConsistentDenoms = [3]string{"bnb", "xrp", "btc"} | ||||
| ) | ||||
| 
 | ||||
| // GenBnbDeputyAddress randomized BnbDeputyAddress
 | ||||
| func GenBnbDeputyAddress(r *rand.Rand) sdk.AccAddress { | ||||
| 	return simulation.RandomAcc(r, Accs).Address | ||||
| } | ||||
| 
 | ||||
| // GenMinBlockLock randomized MinBlockLock
 | ||||
| func GenMinBlockLock(r *rand.Rand) int64 { | ||||
| 	min := int(types.AbsoluteMinimumBlockLock) | ||||
| 	max := int(types.AbsoluteMaximumBlockLock) | ||||
| 	return int64(r.Intn(max-min) + min) | ||||
| } | ||||
| 
 | ||||
| // GenMaxBlockLock randomized MaxBlockLock
 | ||||
| func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 { | ||||
| 	min := int(minBlockLock) | ||||
| 	max := int(types.AbsoluteMaximumBlockLock) | ||||
| 	return int64(r.Intn(max-min) + min) | ||||
| } | ||||
| 
 | ||||
| // GenSupportedAssets gets randomized SupportedAssets
 | ||||
| func GenSupportedAssets(r *rand.Rand) types.AssetParams { | ||||
| 	var assets types.AssetParams | ||||
| 	for i := 0; i < (r.Intn(10) + 1); i++ { | ||||
| 		r := rand.New(rand.NewSource(time.Now().UnixNano())) | ||||
| 		denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) | ||||
| 		asset := genSupportedAsset(r, denom) | ||||
| 		assets = append(assets, asset) | ||||
| 	} | ||||
| 	// Add bnb, btc, or xrp as a supported asset for interactions with other modules
 | ||||
| 	stableAsset := genSupportedAsset(r, ConsistentDenoms[r.Intn(3)]) | ||||
| 	assets = append(assets, stableAsset) | ||||
| 
 | ||||
| 	return assets | ||||
| } | ||||
| 
 | ||||
| func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam { | ||||
| 	coinID, _ := simulation.RandPositiveInt(r, sdk.NewInt(100000)) | ||||
| 	limit, _ := simulation.RandPositiveInt(r, MaxSupplyLimit) | ||||
| 	return types.AssetParam{ | ||||
| 		Denom:  denom, | ||||
| 		CoinID: int(coinID.Int64()), | ||||
| 		Limit:  limit, | ||||
| 		Active: true, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // RandomizedGenState generates a random GenesisState
 | ||||
| func RandomizedGenState(simState *module.SimulationState) { | ||||
| 	Accs = simState.Accounts | ||||
| 
 | ||||
| 	// TODO implement this fully
 | ||||
| 	// - randomly generating the genesis params
 | ||||
| 	// - overwriting with genesis provided to simulation
 | ||||
| 	genesisState := types.DefaultGenesisState() | ||||
| 	bep3Genesis := loadRandomBep3GenState(simState) | ||||
| 	fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis)) | ||||
| 	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bep3Genesis) | ||||
| 
 | ||||
| 	fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState)) | ||||
| 	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) | ||||
| 	authGenesis, totalCoins := loadAuthGenState(simState, bep3Genesis) | ||||
| 	simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) | ||||
| 
 | ||||
| 	// Update supply to match amount of coins in auth
 | ||||
| 	var supplyGenesis supply.GenesisState | ||||
| 	simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) | ||||
| 	for _, deputyCoin := range totalCoins { | ||||
| 		supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin) | ||||
| 	} | ||||
| 	simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) | ||||
| } | ||||
| 
 | ||||
| func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState { | ||||
| 	bnbDeputyAddress := simulation.RandomAcc(simState.Rand, simState.Accounts).Address | ||||
| 
 | ||||
| 	// min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100
 | ||||
| 	minBlockLock := int64(types.AbsoluteMinimumBlockLock) | ||||
| 	maxBlockLock := minBlockLock * 2 | ||||
| 
 | ||||
| 	var supportedAssets types.AssetParams | ||||
| 	simState.AppParams.GetOrGenerate( | ||||
| 		simState.Cdc, SupportedAssets, &supportedAssets, simState.Rand, | ||||
| 		func(r *rand.Rand) { supportedAssets = GenSupportedAssets(r) }, | ||||
| 	) | ||||
| 
 | ||||
| 	bep3Genesis := types.GenesisState{ | ||||
| 		Params: types.Params{ | ||||
| 			BnbDeputyAddress: bnbDeputyAddress, | ||||
| 			MinBlockLock:     minBlockLock, | ||||
| 			MaxBlockLock:     maxBlockLock, | ||||
| 			SupportedAssets:  supportedAssets, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	return bep3Genesis | ||||
| } | ||||
| 
 | ||||
| func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.GenesisState) ( | ||||
| 	auth.GenesisState, []sdk.Coins) { | ||||
| 	var authGenesis auth.GenesisState | ||||
| 	simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) | ||||
| 
 | ||||
| 	deputy, found := getAccount(authGenesis.Accounts, bep3Genesis.Params.BnbDeputyAddress) | ||||
| 	if !found { | ||||
| 		panic("deputy address not found in available accounts") | ||||
| 	} | ||||
| 
 | ||||
| 	// Load total limit of each supported asset to deputy's account
 | ||||
| 	var totalCoins []sdk.Coins | ||||
| 	for _, asset := range bep3Genesis.Params.SupportedAssets { | ||||
| 		assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit)) | ||||
| 		if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin)); err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		totalCoins = append(totalCoins, assetCoin) | ||||
| 	} | ||||
| 	authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, deputy) | ||||
| 
 | ||||
| 	return authGenesis, totalCoins | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| } | ||||
|  | ||||
							
								
								
									
										153
									
								
								x/bep3/simulation/operations/msg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								x/bep3/simulation/operations/msg.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | ||||
| package operations | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/baseapp" | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/auth" | ||||
| 	"github.com/cosmos/cosmos-sdk/x/simulation" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/bep3" | ||||
| 	"github.com/kava-labs/kava/x/bep3/keeper" | ||||
| 	"github.com/kava-labs/kava/x/bep3/types" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	noOpMsg = simulation.NoOpMsg(bep3.ModuleName) | ||||
| ) | ||||
| 
 | ||||
| // SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values
 | ||||
| func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation { | ||||
| 	handler := bep3.NewHandler(k) | ||||
| 
 | ||||
| 	return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( | ||||
| 		simulation.OperationMsg, []simulation.FutureOperation, error) { | ||||
| 
 | ||||
| 		sender := k.GetBnbDeputyAddress(ctx) | ||||
| 		recipient := simulation.RandomAcc(r, accs).Address | ||||
| 
 | ||||
| 		recipientOtherChain := simulation.RandStringOfLength(r, 43) | ||||
| 		senderOtherChain := simulation.RandStringOfLength(r, 43) | ||||
| 
 | ||||
| 		// Generate cryptographically strong pseudo-random number
 | ||||
| 		randomNumber, err := types.GenerateSecureRandomNumber() | ||||
| 		if err != nil { | ||||
| 			return noOpMsg, nil, err | ||||
| 		} | ||||
| 		// Must use current blocktime instead of 'now' since initial blocktime was randomly generated
 | ||||
| 		timestamp := ctx.BlockTime().Unix() | ||||
| 		randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) | ||||
| 
 | ||||
| 		// Randomly select an asset from supported assets
 | ||||
| 		assets, found := k.GetAssets(ctx) | ||||
| 		if !found { | ||||
| 			return noOpMsg, nil, fmt.Errorf("no supported assets found") | ||||
| 		} | ||||
| 		asset := assets[r.Intn(len(assets))] | ||||
| 
 | ||||
| 		// Check that the sender has coins of this type
 | ||||
| 		availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom) | ||||
| 		if !availableAmount.IsPositive() { | ||||
| 			return noOpMsg, nil, fmt.Errorf("available amount must be positive") | ||||
| 		} | ||||
| 
 | ||||
| 		// Get a random amount of the available coins
 | ||||
| 		amount, err := simulation.RandPositiveInt(r, availableAmount) | ||||
| 		if err != nil { | ||||
| 			return noOpMsg, nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		// If we don't adjust the conversion factor, we'll be out of funds soon
 | ||||
| 		adjustedAmount := amount.Int64() / int64(math.Pow10(8)) | ||||
| 		coin := sdk.NewInt64Coin(asset.Denom, adjustedAmount) | ||||
| 		coins := sdk.NewCoins(coin) | ||||
| 		expectedIncome := coin.String() | ||||
| 
 | ||||
| 		// We're assuming that sims are run with -NumBlocks=100
 | ||||
| 		heightSpan := int64(55) | ||||
| 		crossChain := true | ||||
| 
 | ||||
| 		msg := types.NewMsgCreateAtomicSwap( | ||||
| 			sender, recipient, recipientOtherChain, senderOtherChain, randomNumberHash, | ||||
| 			timestamp, coins, expectedIncome, heightSpan, crossChain) | ||||
| 
 | ||||
| 		if err := msg.ValidateBasic(); err != nil { | ||||
| 			return noOpMsg, nil, fmt.Errorf("expected MsgCreateAtomicSwap to pass ValidateBasic: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// Submit msg
 | ||||
| 		ok := submitMsg(ctx, handler, msg) | ||||
| 
 | ||||
| 		// If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation
 | ||||
| 		var futureOp simulation.FutureOperation | ||||
| 		if ok { | ||||
| 			swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain) | ||||
| 			acc := simulation.RandomAcc(r, accs) | ||||
| 			evenOdd := r.Intn(2) + 1 | ||||
| 			if evenOdd%2 == 0 { | ||||
| 				// Claim future operation
 | ||||
| 				executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) | ||||
| 				futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler) | ||||
| 			} else { | ||||
| 				// Refund future operation
 | ||||
| 				executionBlock := ctx.BlockHeight() + msg.HeightSpan | ||||
| 				futureOp = loadRefundFutureOp(acc.Address, swapID, executionBlock, handler) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return simulation.NewOperationMsg(msg, ok, ""), []simulation.FutureOperation{futureOp}, nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func loadClaimFutureOp(sender sdk.AccAddress, swapID []byte, randomNumber []byte, height int64, handler sdk.Handler) simulation.FutureOperation { | ||||
| 	claimOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( | ||||
| 		simulation.OperationMsg, []simulation.FutureOperation, error) { | ||||
| 
 | ||||
| 		// Build the refund msg and validate basic
 | ||||
| 		claimMsg := types.NewMsgClaimAtomicSwap(sender, swapID, randomNumber) | ||||
| 		if err := claimMsg.ValidateBasic(); err != nil { | ||||
| 			return noOpMsg, nil, fmt.Errorf("expected MsgClaimAtomicSwap to pass ValidateBasic: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// Test msg submission at target block height
 | ||||
| 		ok := handler(ctx.WithBlockHeight(height), claimMsg).IsOK() | ||||
| 		return simulation.NewOperationMsg(claimMsg, ok, ""), nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return simulation.FutureOperation{ | ||||
| 		BlockHeight: int(height), | ||||
| 		Op:          claimOp, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func loadRefundFutureOp(sender sdk.AccAddress, swapID []byte, height int64, handler sdk.Handler) simulation.FutureOperation { | ||||
| 	refundOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( | ||||
| 		simulation.OperationMsg, []simulation.FutureOperation, error) { | ||||
| 		// Build the refund msg and validate basic
 | ||||
| 		refundMsg := types.NewMsgRefundAtomicSwap(sender, swapID) | ||||
| 		if err := refundMsg.ValidateBasic(); err != nil { | ||||
| 			return noOpMsg, nil, fmt.Errorf("expected MsgRefundAtomicSwap to pass ValidateBasic: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		// Test msg submission at target block height
 | ||||
| 		ok := handler(ctx.WithBlockHeight(height), refundMsg).IsOK() | ||||
| 		return simulation.NewOperationMsg(refundMsg, ok, ""), nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return simulation.FutureOperation{ | ||||
| 		BlockHeight: int(height), | ||||
| 		Op:          refundOp, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { | ||||
| 	ctx, write := ctx.CacheContext() | ||||
| 	ok = handler(ctx, msg).IsOK() | ||||
| 	if ok { | ||||
| 		write() | ||||
| 	} | ||||
| 	return ok | ||||
| } | ||||
| @ -1,14 +1,46 @@ | ||||
| package simulation | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 
 | ||||
| 	"github.com/cosmos/cosmos-sdk/x/simulation" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/bep3/types" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	keyBnbDeputyAddress = "BnbDeputyAddress" | ||||
| 	keyMinBlockLock     = "MinBlockLock" | ||||
| 	keyMaxBlockLock     = "MaxBlockLock" | ||||
| 	keySupportedAssets  = "SupportedAssets" | ||||
| ) | ||||
| 
 | ||||
| // ParamChanges defines the parameters that can be modified by param change proposals
 | ||||
| // on the simulation
 | ||||
| func ParamChanges(r *rand.Rand) []simulation.ParamChange { | ||||
| 	// TODO implement this
 | ||||
| 	return []simulation.ParamChange{} | ||||
| 	// We generate MinBlockLock first because the result is required by GenMaxBlockLock()
 | ||||
| 	minBlockLockVal := GenMinBlockLock(r) | ||||
| 
 | ||||
| 	return []simulation.ParamChange{ | ||||
| 		simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, "", | ||||
| 			func(r *rand.Rand) string { | ||||
| 				return fmt.Sprintf("\"%s\"", GenBnbDeputyAddress(r)) | ||||
| 			}, | ||||
| 		), | ||||
| 		simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, "", | ||||
| 			func(r *rand.Rand) string { | ||||
| 				return fmt.Sprintf("\"%d\"", minBlockLockVal) | ||||
| 			}, | ||||
| 		), | ||||
| 		simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, "", | ||||
| 			func(r *rand.Rand) string { | ||||
| 				return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal)) | ||||
| 			}, | ||||
| 		), | ||||
| 		simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, "", | ||||
| 			func(r *rand.Rand) string { | ||||
| 				return fmt.Sprintf("\"%v\"", GenSupportedAssets(r)) | ||||
| 			}, | ||||
| 		), | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Denali Marsh
						Denali Marsh