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 := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64)) 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.BigInt().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.BigInt().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 }