From c1422aa8b51a5206021dff82f44be23b4fdaf37b Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 15:30:56 +0900 Subject: [PATCH 01/25] feat: simulation for `coinswap` module --- app/params/weights.go | 16 +++ x/coinswap/module.go | 7 +- x/coinswap/simulation/decoder.go | 37 +++++- x/coinswap/simulation/decoder_test.go | 61 +++++++++ x/coinswap/simulation/genesis.go | 75 ++++++++++- x/coinswap/simulation/genesis_test.go | 81 ++++++++++++ x/coinswap/simulation/operation_test.go | 127 ++++++++++++++++++ x/coinswap/simulation/operations.go | 163 +++++++++++------------- x/coinswap/simulation/params.go | 25 ---- x/coinswap/simulation/proposal.go | 52 ++++++++ x/coinswap/simulation/proposal_test.go | 48 +++++++ x/coinswap/types/msgs.go | 148 ++++++++++++++++++++- 12 files changed, 720 insertions(+), 120 deletions(-) create mode 100644 app/params/weights.go create mode 100644 x/coinswap/simulation/decoder_test.go create mode 100644 x/coinswap/simulation/genesis_test.go create mode 100644 x/coinswap/simulation/operation_test.go delete mode 100644 x/coinswap/simulation/params.go create mode 100644 x/coinswap/simulation/proposal.go create mode 100644 x/coinswap/simulation/proposal_test.go diff --git a/app/params/weights.go b/app/params/weights.go new file mode 100644 index 00000000..661c8b76 --- /dev/null +++ b/app/params/weights.go @@ -0,0 +1,16 @@ +package params + +const ( + DefaultWeightRegisterCoinProposal int = 5 + DefaultWeightRegisterERC20Proposal int = 5 + DefaultWeightToggleTokenConversionProposal int = 5 + DefaultWeightLendingMarketProposal int = 5 + DefaultWeightTreasuryProposal int = 5 + + DefaultWeightMsgConvertCoin int = 20 + DefaultWeightMsgConvertErc20 int = 20 + + DefaultWeightMsgSwapOrder int = 10 + DefaultWeightMsgAddLiquidity int = 20 + DefaultWeightMsgRemoveLiquidity int = 10 +) diff --git a/x/coinswap/module.go b/x/coinswap/module.go index 42c68241..55f9ebf8 100644 --- a/x/coinswap/module.go +++ b/x/coinswap/module.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -152,9 +151,9 @@ func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.We return nil } -// RandomizedParams creates randomized coinswap param changes for the simulator. -func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { - return simulation.ParamChanges(r) +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() } // RegisterStoreDecoder registers a decoder for coinswap module's types diff --git a/x/coinswap/simulation/decoder.go b/x/coinswap/simulation/decoder.go index 554f894e..015f3e83 100644 --- a/x/coinswap/simulation/decoder.go +++ b/x/coinswap/simulation/decoder.go @@ -1,10 +1,41 @@ package simulation import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/coinswap/types" ) -// DecodeStore unmarshals the KVPair's Value to the corresponding htlc type -func DecodeStore(kvA, kvB kv.Pair) string { - return "" +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:], []byte(types.KeyPool)): + var pA, pB types.Pool + cdc.MustUnmarshal(kvA.Value, &pA) + cdc.MustUnmarshal(kvB.Value, &pB) + return fmt.Sprintf("%v\n%v", pA, pB) + + case bytes.Equal(kvA.Key[:], []byte(types.KeyNextPoolSequence)): + var seqA, seqB uint64 + seqA = sdk.BigEndianToUint64(kvA.Value) + seqB = sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("%v\n%v", seqA, seqB) + + case bytes.Equal(kvA.Key[:], []byte(types.KeyPoolLptDenom)): + var pA, pB types.Pool + cdc.MustUnmarshal(kvA.Value, &pA) + cdc.MustUnmarshal(kvB.Value, &pB) + return fmt.Sprintf("%v\n%v", pA, pB) + + default: + panic(fmt.Sprintf("invalid coinswap key prefix %X", kvA.Key[:1])) + } + } } diff --git a/x/coinswap/simulation/decoder_test.go b/x/coinswap/simulation/decoder_test.go new file mode 100644 index 00000000..c8c44140 --- /dev/null +++ b/x/coinswap/simulation/decoder_test.go @@ -0,0 +1,61 @@ +package simulation_test + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + module "github.com/Canto-Network/Canto/v7/x/coinswap" + "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" + "github.com/cosmos/cosmos-sdk/types/kv" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" +) + +func TestCoinSwapStore(t *testing.T) { + encodingConfig := moduletestutil.MakeTestEncodingConfig(module.AppModuleBasic{}) + cdc := encodingConfig.Codec + dec := simulation.NewDecodeStore(cdc) + + pool := types.Pool{ + Id: types.GetPoolId("denom1"), + StandardDenom: "denom2", + CounterpartyDenom: "denom1", + EscrowAddress: types.GetReservePoolAddr("lptDenom").String(), + LptDenom: "lptDenom", + } + + sequence := uint64(1) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: []byte(types.KeyPool), Value: cdc.MustMarshal(&pool)}, + {Key: []byte(types.KeyPoolLptDenom), Value: cdc.MustMarshal(&pool)}, + {Key: []byte(types.KeyNextPoolSequence), Value: sdk.Uint64ToBigEndian(sequence)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Pool", fmt.Sprintf("%v\n%v", pool, pool)}, + {"PoolLptDenom", fmt.Sprintf("%v\n%v", pool, pool)}, + {"NextPoolSequence", fmt.Sprintf("%v\n%v", sequence, sequence)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/coinswap/simulation/genesis.go b/x/coinswap/simulation/genesis.go index 98b3a650..9bd2ef67 100644 --- a/x/coinswap/simulation/genesis.go +++ b/x/coinswap/simulation/genesis.go @@ -1,8 +1,81 @@ package simulation import ( + "encoding/json" + "fmt" + "math/rand" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// simulation parameter constants +const ( + fee = "fee" + poolCreationFee = "pool_creation_fee" + taxRate = "tax_rate" + maxStandardCoinPerPool = "max_standard_coin_per_pool" + maxSwapAmount = "max_swap_amount" ) +func generateRandomFee(r *rand.Rand) sdkmath.LegacyDec { + return sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 10)), 3) +} + +func generateRandomPoolCreationFee(r *rand.Rand) sdk.Coin { + return sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 0, 1000000))) +} + +func generateRandomTaxRate(r *rand.Rand) sdkmath.LegacyDec { + return sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 10)), 3) +} + +func generateRandomMaxStandardCoinPerPool(r *rand.Rand) sdkmath.Int { + return sdkmath.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 0, 10000)), 18) +} + +func generateRandomMaxSwapAmount(r *rand.Rand) sdk.Coins { + return sdk.NewCoins( + sdk.NewCoin(types.UsdcIBCDenom, sdkmath.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 6)), + sdk.NewCoin(types.UsdtIBCDenom, sdkmath.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 6)), + sdk.NewCoin(types.EthIBCDenom, sdkmath.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 16)), + ) +} + // RandomizedGenState generates a random GenesisState for coinswap -func RandomizedGenState(simState *module.SimulationState) {} +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesisState() + + simState.AppParams.GetOrGenerate( + fee, &genesis.Params.Fee, simState.Rand, + func(r *rand.Rand) { genesis.Params.Fee = generateRandomFee(r) }, + ) + + simState.AppParams.GetOrGenerate( + poolCreationFee, &genesis.Params.PoolCreationFee, simState.Rand, + func(r *rand.Rand) { genesis.Params.PoolCreationFee = generateRandomPoolCreationFee(r) }, + ) + + simState.AppParams.GetOrGenerate( + taxRate, &genesis.Params.TaxRate, simState.Rand, + func(r *rand.Rand) { genesis.Params.TaxRate = generateRandomTaxRate(r) }, + ) + + simState.AppParams.GetOrGenerate( + maxStandardCoinPerPool, &genesis.Params.MaxStandardCoinPerPool, simState.Rand, + func(r *rand.Rand) { genesis.Params.MaxStandardCoinPerPool = generateRandomMaxStandardCoinPerPool(r) }, + ) + + simState.AppParams.GetOrGenerate( + maxSwapAmount, &genesis.Params.MaxSwapAmount, simState.Rand, + func(r *rand.Rand) { genesis.Params.MaxSwapAmount = generateRandomMaxSwapAmount(r) }, + ) + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated coinswap parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) + +} diff --git a/x/coinswap/simulation/genesis_test.go b/x/coinswap/simulation/genesis_test.go new file mode 100644 index 00000000..f2e86669 --- /dev/null +++ b/x/coinswap/simulation/genesis_test.go @@ -0,0 +1,81 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: math.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + require.Equal(t, math.LegacyNewDecWithPrec(4, 3), genState.Params.Fee) + require.Equal(t, sdk.NewInt64Coin(sdk.DefaultBondDenom, 163511), genState.Params.PoolCreationFee) + require.Equal(t, math.LegacyNewDecWithPrec(6, 3), genState.Params.TaxRate) + require.Equal(t, math.NewIntWithDecimal(3310, 18), genState.Params.MaxStandardCoinPerPool) + require.Equal(t, sdk.NewCoins( + sdk.NewCoin(types.UsdcIBCDenom, math.NewIntWithDecimal(70, 6)), + sdk.NewCoin(types.UsdtIBCDenom, math.NewIntWithDecimal(52, 6)), + sdk.NewCoin(types.EthIBCDenom, math.NewIntWithDecimal(65, 16)), + ), genState.Params.MaxSwapAmount) + +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} diff --git a/x/coinswap/simulation/operation_test.go b/x/coinswap/simulation/operation_test.go new file mode 100644 index 00000000..5f7e58b4 --- /dev/null +++ b/x/coinswap/simulation/operation_test.go @@ -0,0 +1,127 @@ +package simulation_test + +import ( + "math/rand" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/Canto-Network/Canto/v7/app" + "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" +) + +func TestWeightedOperations(t *testing.T) { + canto, ctx := createTestApp(t, false) + cdc := types.ModuleCdc + appParams := make(simtypes.AppParams) + + weightedOps := simulation.WeightedOperations( + appParams, + cdc, + canto.CoinswapKeeper, + canto.AccountKeeper, + canto.BankKeeper, + ) + + s := rand.NewSource(2) + r := rand.New(s) + accs := getTestingAccounts(t, r, canto, ctx, 10) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + {params.DefaultWeightMsgAddLiquidity, types.ModuleName, types.TypeMsgAddLiquidity}, + {params.DefaultWeightMsgSwapOrder, types.ModuleName, types.TypeMsgSwapOrder}, + {params.DefaultWeightMsgRemoveLiquidity, types.ModuleName, types.TypeMsgRemoveLiquidity}, + } + + for i, w := range weightedOps { + opMsg, _, _ := w.Op()(r, canto.BaseApp, ctx, accs, ctx.ChainID()) + require.Equal(t, expected[i].weight, w.Weight()) + require.Equal(t, expected[i].opMsgRoute, opMsg.Route) + require.Equal(t, expected[i].opMsgName, opMsg.Name) + } +} + +func createTestApp(t *testing.T, isCheckTx bool) (*app.Canto, sdk.Context) { + app := app.Setup(isCheckTx, nil) + r := rand.New(rand.NewSource(1)) + + simAccs := simtypes.RandomAccounts(r, 10) + + ctx := app.BaseApp.NewContext(isCheckTx) + validator := getTestingValidator0(t, app, ctx, simAccs) + consAddr, err := validator.GetConsAddr() + require.NoError(t, err) + ctx = ctx.WithBlockHeader(cmtproto.Header{Height: 1, + ChainID: "canto_9001-1", + Time: time.Now().UTC(), + ProposerAddress: consAddr, + }) + return app, ctx +} + +func getTestingAccounts(t *testing.T, r *rand.Rand, app *app.Canto, ctx sdk.Context, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 100_000_000) + initCoins := sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, initAmt), + ) + + // add coins to the accounts + for _, account := range accounts { + acc := app.AccountKeeper.NewAccountWithAddress(ctx, account.Address) + app.AccountKeeper.SetAccount(ctx, acc) + err := fundAccount(app.BankKeeper, ctx, account.Address, initCoins) + require.NoError(t, err) + } + + return accounts +} + +func fundAccount(bk bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error { + if err := bk.MintCoins(ctx, types.ModuleName, coins); err != nil { + return err + } + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins); err != nil { + return err + } + return nil +} + +func getTestingValidator0(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account) stakingtypes.Validator { + commission0 := stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()) + return getTestingValidator(t, app, ctx, accounts, commission0, 0) +} + +func getTestingValidator(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account, commission stakingtypes.Commission, n int) stakingtypes.Validator { + account := accounts[n] + valPubKey := account.PubKey + valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) + validator := testutil.NewValidator(t, valAddr, valPubKey) + validator, err := validator.SetInitialCommission(commission) + require.NoError(t, err) + + validator.DelegatorShares = sdkmath.LegacyNewDec(100) + validator.Tokens = app.StakingKeeper.TokensFromConsensusPower(ctx, 100) + + app.StakingKeeper.SetValidator(ctx, validator) + app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) + + return validator +} diff --git a/x/coinswap/simulation/operations.go b/x/coinswap/simulation/operations.go index 2b2f7fbf..d1b25848 100644 --- a/x/coinswap/simulation/operations.go +++ b/x/coinswap/simulation/operations.go @@ -8,22 +8,17 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - simappparams "cosmossdk.io/simapp/params" - "github.com/Canto-Network/Canto/v7/x/coinswap/keeper" - "github.com/Canto-Network/Canto/v7/x/coinswap/types" + "github.com/Canto-Network/Canto/v7/app/params" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" -) -// coinswap message types -var ( - TypeMsgAddLiquidity = sdk.MsgTypeURL(&types.MsgAddLiquidity{}) - TypeMsgRemoveLiquidity = sdk.MsgTypeURL(&types.MsgRemoveLiquidity{}) - TypeMsgSwapOrder = sdk.MsgTypeURL(&types.MsgSwapOrder{}) + "github.com/Canto-Network/Canto/v7/x/coinswap/keeper" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" ) // Simulation operation weights constants @@ -49,21 +44,21 @@ func WeightedOperations( appParams.GetOrGenerate( OpWeightMsgSwapOrder, &weightSwap, nil, func(_ *rand.Rand) { - weightSwap = 50 + weightSwap = params.DefaultWeightMsgSwapOrder }, ) appParams.GetOrGenerate( OpWeightMsgAddLiquidity, &weightAdd, nil, func(_ *rand.Rand) { - weightAdd = 100 + weightAdd = params.DefaultWeightMsgAddLiquidity }, ) appParams.GetOrGenerate( OpWeightMsgRemoveLiquidity, &weightRemove, nil, func(_ *rand.Rand) { - weightRemove = 30 + weightRemove = params.DefaultWeightMsgRemoveLiquidity }, ) @@ -99,34 +94,36 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B minLiquidity sdkmath.Int ) - standardDenom, err := k.GetStandardDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "failed to get standardDenom"), nil, nil - } - + standardDenom, _ := k.GetStandardDenom(ctx) spendable := bk.SpendableCoins(ctx, account.GetAddress()) exactStandardAmt := simtypes.RandomAmount(r, spendable.AmountOf(standardDenom)) + if !exactStandardAmt.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "standardAmount should be positive"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "standardAmount should be positive"), nil, nil } maxToken = RandomSpendableToken(r, spendable) if maxToken.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be standardDenom"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "tokenDenom should not be standardDenom"), nil, err + } + + _, err = k.GetMaximumSwapAmount(ctx, maxToken.Denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, err.Error()), nil, nil } if strings.HasPrefix(maxToken.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "tokenDenom should not be liquidity token"), nil, err } if !maxToken.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "maxToken must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "maxToken must is positive"), nil, err } poolId := types.GetPoolId(maxToken.Denom) pool, has := k.GetPool(ctx, poolId) if has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "pool not found"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "pool not found"), nil, err } reservePool, err := k.GetPoolBalances(ctx, pool.EscrowAddress) @@ -139,7 +136,7 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B minLiquidity = liquidity.Mul(exactStandardAmt).Quo(standardReserveAmt) if !maxToken.Amount.Sub(reservePool.AmountOf(maxToken.GetDenom()).Mul(exactStandardAmt).Quo(standardReserveAmt)).IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "insufficient funds"), nil, err } params := k.GetParams(ctx) @@ -150,7 +147,7 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B spendTotal = spendTotal.Add(exactStandardAmt) } if spendable.AmountOf(poolCreationFee.Denom).LT(spendTotal) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "insufficient funds"), nil, err } } @@ -164,17 +161,15 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B ) var fees sdk.Coins - coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoin(standardDenom, exactStandardAmt)) - coinsTemp, hasNeg = coinsTemp.SafeSub(maxToken) - + coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoins(sdk.NewCoin(standardDenom, exactStandardAmt), maxToken)...) if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil } } - txGen := simappparams.MakeTestEncodingConfig().TxConfig + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig tx, err := simtestutil.GenSignedMockTx( r, @@ -189,11 +184,11 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err } - if _, _, err := app.SimTxFinalizeBlock(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err + if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(msg, true, ""), nil, nil @@ -216,34 +211,36 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank simAccount, _ := simtypes.RandomAcc(r, accs) account := ak.GetAccount(ctx, simAccount.Address) spendable := bk.SpendableCoins(ctx, account.GetAddress()) - standardDenom, err := k.GetStandardDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "failed to get standardDenom"), nil, err - } + standardDenom, _ := k.GetStandardDenom(ctx) if spendable.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "spendable is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "spendable is zero"), nil, err } // sold coin inputCoin = RandomSpendableToken(r, spendable) + _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + } + if strings.HasPrefix(inputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should not be liquidity token"), nil, err } if !inputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin must is positive"), nil, err } poolId := types.GetPoolId(inputCoin.Denom) pool, has := k.GetPool(ctx, poolId) if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } // bought coin @@ -253,29 +250,34 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank return false }) if coins.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "total supply is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "total supply is zero"), nil, err } outputCoin = RandomTotalToken(r, coins) + _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + } + if strings.HasPrefix(outputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin should not be liquidity token"), nil, err } if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin must is positive"), nil, err } poolId = types.GetPoolId(outputCoin.Denom) pool, has = k.GetPool(ctx, poolId) if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if outputCoin.Denom == inputCoin.Denom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin denom and inputcoin denom should not be the same"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin denom and inputcoin denom should not be the same"), nil, nil } isDoubleSwap := (outputCoin.Denom != standardDenom) && (inputCoin.Denom != standardDenom) @@ -284,26 +286,26 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank if isBuyOrder && isDoubleSwap { inputCoin, outputCoin, err = doubleSwapBill(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil } } else if isBuyOrder && !isDoubleSwap { inputCoin, outputCoin, err = singleSwapBill(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil } } else if !isBuyOrder && isDoubleSwap { inputCoin, outputCoin, err = doubleSwapSellOrder(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil } } else if !isBuyOrder && !isDoubleSwap { inputCoin, outputCoin, err = singleSwapSellOrder(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil } } if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin must is positive"), nil, err } deadline := randDeadline(r) @@ -321,15 +323,15 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank ) var fees sdk.Coins - coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoin(inputCoin.Denom, inputCoin.Amount)) + coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoins(sdk.NewCoin(inputCoin.Denom, inputCoin.Amount))...) if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil } } - txGen := simappparams.MakeTestEncodingConfig().TxConfig + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig tx, err := simtestutil.GenSignedMockTx( r, txGen, @@ -343,11 +345,11 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err } - if _, _, err := app.SimTxFinalizeBlock(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err + if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(msg, true, ""), nil, nil @@ -363,10 +365,7 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type ) { simAccount, _ := simtypes.RandomAcc(r, accs) account := ak.GetAccount(ctx, simAccount.Address) - standardDenom, err := k.GetStandardDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "failed to get standardDenom"), nil, err - } + standardDenom, _ := k.GetStandardDenom(ctx) var ( minToken sdkmath.Int @@ -376,23 +375,23 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type spendable := bk.SpendableCoins(ctx, account.GetAddress()) if spendable.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "spendable is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "spendable is zero"), nil, err } token := RandomSpendableToken(r, spendable) if token.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "tokenDenom should not be standardDenom"), nil, err + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "tokenDenom should not be standardDenom"), nil, err } pool, has := k.GetPoolByLptDenom(ctx, token.Denom) if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } standardReserveAmt := reservePool.AmountOf(standardDenom) @@ -402,20 +401,20 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type liquidityReserve := bk.GetSupply(ctx, token.Denom).Amount if !withdrawLiquidity.IsValid() || !withdrawLiquidity.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "invalid withdrawLiquidity"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "invalid withdrawLiquidity"), nil, nil } if liquidityReserve.LT(withdrawLiquidity.Amount) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } minToken = withdrawLiquidity.Amount.Mul(tokenReserveAmt).Quo(liquidityReserve) if tokenReserveAmt.LT(minToken) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } minStandardAmt = withdrawLiquidity.Amount.Mul(standardReserveAmt).Quo(liquidityReserve) if standardReserveAmt.LT(minStandardAmt) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } deadline := randDeadline(r) @@ -428,16 +427,15 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type ) var fees sdk.Coins - coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoin(pool.CounterpartyDenom, minToken)) - coinsTemp, hasNeg = coinsTemp.SafeSub(sdk.NewCoin(standardDenom, minStandardAmt)) + coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoins(sdk.NewCoin(pool.CounterpartyDenom, minToken), sdk.NewCoin(standardDenom, minStandardAmt))...) if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil } } - txGen := simappparams.MakeTestEncodingConfig().TxConfig + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig tx, err := simtestutil.GenSignedMockTx( r, @@ -452,11 +450,11 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err } - if _, _, err := app.SimTxFinalizeBlock(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, nil + if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, nil } return simtypes.NewOperationMsg(msg, true, ""), nil, nil @@ -485,11 +483,7 @@ func randBoolean(r *rand.Rand) bool { // Double swap bill func doubleSwapBill(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Keeper) (sdk.Coin, sdk.Coin, error) { - standardDenom, err := k.GetStandardDenom(ctx) - if err != nil { - return sdk.Coin{}, sdk.Coin{}, err - } - + standardDenom, _ := k.GetStandardDenom(ctx) param := k.GetParams(ctx) // generate sold standard Coin @@ -530,10 +524,7 @@ func singleSwapBill(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Ke // Double swap sell orders func doubleSwapSellOrder(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Keeper) (sdk.Coin, sdk.Coin, error) { - standardDenom, err := k.GetStandardDenom(ctx) - if err != nil { - return sdk.Coin{}, sdk.Coin{}, err - } + standardDenom, _ := k.GetStandardDenom(ctx) param := k.GetParams(ctx) diff --git a/x/coinswap/simulation/params.go b/x/coinswap/simulation/params.go deleted file mode 100644 index f5e8f815..00000000 --- a/x/coinswap/simulation/params.go +++ /dev/null @@ -1,25 +0,0 @@ -package simulation - -import ( - "fmt" - "math/rand" - - sdkmath "cosmossdk.io/math" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - - "github.com/Canto-Network/Canto/v7/x/coinswap/types" -) - -// ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation -func ParamChanges(r *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{ - simulation.NewSimLegacyParamChange( - types.ModuleName, string(types.KeyFee), - func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", sdkmath.LegacyNewDecWithPrec(r.Int63n(3), 3)) // 0.1%~0.3% - }, - ), - } -} diff --git a/x/coinswap/simulation/proposal.go b/x/coinswap/simulation/proposal.go new file mode 100644 index 00000000..08ca66f7 --- /dev/null +++ b/x/coinswap/simulation/proposal.go @@ -0,0 +1,52 @@ +package simulation + +import ( + "math/rand" + + math "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateMsgUpdateParams, + ), + } +} + +// SimulateMsgUpdateParams returns a random MsgUpdateParams +func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + params := types.DefaultParams() + params.Fee = math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 10)), 3) + params.PoolCreationFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, int64(simtypes.RandIntBetween(r, 0, 1000000))) + params.TaxRate = math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 10)), 3) + params.MaxStandardCoinPerPool = math.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 0, 1000000)), 18) + params.MaxSwapAmount = sdk.NewCoins( + sdk.NewCoin(types.UsdcIBCDenom, math.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 6)), + sdk.NewCoin(types.UsdtIBCDenom, math.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 6)), + sdk.NewCoin(types.EthIBCDenom, math.NewIntWithDecimal(int64(simtypes.RandIntBetween(r, 1, 100)), 16)), + ) + + return &types.MsgUpdateParams{ + Authority: authority.String(), + Params: params, + } +} diff --git a/x/coinswap/simulation/proposal_test.go b/x/coinswap/simulation/proposal_test.go new file mode 100644 index 00000000..d0bde3a2 --- /dev/null +++ b/x/coinswap/simulation/proposal_test.go @@ -0,0 +1,48 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" + "github.com/Canto-Network/Canto/v7/x/coinswap/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 1, len(weightedProposalMsgs)) + + w0 := weightedProposalMsgs[0] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*types.MsgUpdateParams) + require.True(t, ok) + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Authority) + require.Equal(t, math.LegacyNewDecWithPrec(0, 3), msgUpdateParams.Params.Fee) + require.Equal(t, sdk.NewInt64Coin(sdk.DefaultBondDenom, 240456), msgUpdateParams.Params.PoolCreationFee) + require.Equal(t, math.LegacyNewDecWithPrec(0, 3), msgUpdateParams.Params.TaxRate) + require.Equal(t, math.NewIntWithDecimal(410694, 18), msgUpdateParams.Params.MaxStandardCoinPerPool) + require.Equal(t, sdk.NewCoins( + sdk.NewCoin(types.UsdcIBCDenom, math.NewIntWithDecimal(89, 6)), + sdk.NewCoin(types.UsdtIBCDenom, math.NewIntWithDecimal(22, 6)), + sdk.NewCoin(types.EthIBCDenom, math.NewIntWithDecimal(12, 16)), + ), msgUpdateParams.Params.MaxSwapAmount) +} diff --git a/x/coinswap/types/msgs.go b/x/coinswap/types/msgs.go index e064bb1c..13c5a0ce 100644 --- a/x/coinswap/types/msgs.go +++ b/x/coinswap/types/msgs.go @@ -1,8 +1,10 @@ package types import ( + errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) var ( @@ -16,14 +18,25 @@ const ( LptTokenPrefix = "lpt" // LptTokenFormat defines the name of liquidity token LptTokenFormat = "lpt-%d" + + // TypeMsgAddLiquidity defines the type of MsgAddLiquidity + TypeMsgAddLiquidity = "add_liquidity" + // TypeMsgRemoveLiquidity defines the type of MsgRemoveLiquidity + TypeMsgRemoveLiquidity = "remove_liquidity" + // TypeMsgSwapOrder defines the type of MsgSwapOrder + TypeMsgSwapOrder = "swap_order" ) +/* --------------------------------------------------------------------------- */ +// MsgSwapOrder +/* --------------------------------------------------------------------------- */ + // MsgSwapOrder - struct for swapping a coin // Input and Output can either be exact or calculated. // An exact coin has the senders desired buy or sell amount. // A calculated coin has the desired denomination and bounded amount // the sender is willing to buy or sell in this order. -// + // NewMsgSwapOrder creates a new MsgSwapOrder object. func NewMsgSwapOrder( input Input, @@ -39,6 +52,47 @@ func NewMsgSwapOrder( } } +// Route implements Msg. +func (msg MsgSwapOrder) Route() string { return RouterKey } + +// Type implements Msg. +func (msg MsgSwapOrder) Type() string { return TypeMsgSwapOrder } + +// ValidateBasic implements Msg. +func (msg MsgSwapOrder) ValidateBasic() error { + if err := ValidateInput(msg.Input); err != nil { + return err + } + + if err := ValidateOutput(msg.Output); err != nil { + return err + } + + if msg.Input.Coin.Denom == msg.Output.Coin.Denom { + return errorsmod.Wrapf(ErrEqualDenom, "invalid swap") + } + + return ValidateDeadline(msg.Deadline) +} + +// GetSignBytes implements Msg. +func (msg MsgSwapOrder) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements Msg. +func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { + from, err := sdk.AccAddressFromBech32(msg.Input.Address) + if err != nil { + panic(err) + } + return []sdk.AccAddress{from} +} + +/* --------------------------------------------------------------------------- */ +// MsgAddLiquidity +/* --------------------------------------------------------------------------- */ + // NewMsgAddLiquidity creates a new MsgAddLiquidity object. func NewMsgAddLiquidity( maxToken sdk.Coin, @@ -56,6 +110,54 @@ func NewMsgAddLiquidity( } } +// Route implements Msg. +func (msg MsgAddLiquidity) Route() string { return RouterKey } + +// Type implements Msg. +func (msg MsgAddLiquidity) Type() string { return TypeMsgAddLiquidity } + +// ValidateBasic implements Msg. +func (msg MsgAddLiquidity) ValidateBasic() error { + if err := ValidateMaxToken(msg.MaxToken); err != nil { + return err + } + + if err := ValidateExactStandardAmt(msg.ExactStandardAmt); err != nil { + return err + } + + if err := ValidateMinLiquidity(msg.MinLiquidity); err != nil { + return err + } + + if err := ValidateDeadline(msg.Deadline); err != nil { + return err + } + + if _, err := sdk.AccAddressFromBech32(msg.Sender); err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) + } + return nil +} + +// GetSignBytes implements Msg. +func (msg MsgAddLiquidity) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements Msg. +func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { + from, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{from} +} + +/* --------------------------------------------------------------------------- */ +// MsgRemoveLiquidity +/* --------------------------------------------------------------------------- */ + // NewMsgRemoveLiquidity creates a new MsgRemoveLiquidity object func NewMsgRemoveLiquidity( minToken sdkmath.Int, @@ -72,3 +174,47 @@ func NewMsgRemoveLiquidity( Sender: sender, } } + +// Route implements Msg. +func (msg MsgRemoveLiquidity) Route() string { return RouterKey } + +// Type implements Msg. +func (msg MsgRemoveLiquidity) Type() string { return TypeMsgRemoveLiquidity } + +// ValidateBasic implements Msg. +func (msg MsgRemoveLiquidity) ValidateBasic() error { + if err := ValidateMinToken(msg.MinToken); err != nil { + return err + } + + if err := ValidateWithdrawLiquidity(msg.WithdrawLiquidity); err != nil { + return err + } + + if err := ValidateMinStandardAmt(msg.MinStandardAmt); err != nil { + return err + } + + if err := ValidateDeadline(msg.Deadline); err != nil { + return err + } + + if _, err := sdk.AccAddressFromBech32(msg.Sender); err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) + } + return nil +} + +// GetSignBytes implements Msg. +func (msg MsgRemoveLiquidity) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements Msg. +func (msg MsgRemoveLiquidity) GetSigners() []sdk.AccAddress { + from, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{from} +} From 683e13073c709f51528bfb652ad2aefb5f2c8f1b Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 16:17:38 +0900 Subject: [PATCH 02/25] feat: simulation for `csr` module --- x/csr/module.go | 7 +++ x/csr/simulation/decoder.go | 42 +++++++++++++++++ x/csr/simulation/decoder_test.go | 61 +++++++++++++++++++++++++ x/csr/simulation/genesis.go | 48 ++++++++++++++++++++ x/csr/simulation/genesis_test.go | 72 ++++++++++++++++++++++++++++++ x/csr/simulation/proposals.go | 44 ++++++++++++++++++ x/csr/simulation/proposals_test.go | 42 +++++++++++++++++ 7 files changed, 316 insertions(+) create mode 100644 x/csr/simulation/decoder.go create mode 100644 x/csr/simulation/decoder_test.go create mode 100644 x/csr/simulation/genesis.go create mode 100644 x/csr/simulation/genesis_test.go create mode 100644 x/csr/simulation/proposals.go create mode 100644 x/csr/simulation/proposals_test.go diff --git a/x/csr/module.go b/x/csr/module.go index 1a20eaab..5ee38fc5 100644 --- a/x/csr/module.go +++ b/x/csr/module.go @@ -7,6 +7,8 @@ import ( // this line is used by starport scaffolding # 1 + "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -168,3 +170,8 @@ func (am AppModule) BeginBlock(ctx context.Context) error { } return nil } + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} diff --git a/x/csr/simulation/decoder.go b/x/csr/simulation/decoder.go new file mode 100644 index 00000000..59fbbeee --- /dev/null +++ b/x/csr/simulation/decoder.go @@ -0,0 +1,42 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/ethereum/go-ethereum/common" + + "github.com/Canto-Network/Canto/v7/x/csr/keeper" + "github.com/Canto-Network/Canto/v7/x/csr/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.KeyPrefixCSR): + var cA, cB types.CSR + cdc.MustUnmarshal(kvA.Value, &cA) + cdc.MustUnmarshal(kvB.Value, &cB) + return fmt.Sprintf("%v\n%v", cA, cB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixContract): + var nftA, nftB uint64 + nftA = keeper.BytesToUInt64(kvA.Value) + nftB = keeper.BytesToUInt64(kvB.Value) + return fmt.Sprintf("%v\n%v", nftA, nftB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixAddrs): + var tsA, tsB common.Address + tsA = common.BytesToAddress(kvA.Value) + tsB = common.BytesToAddress(kvB.Value) + return fmt.Sprintf("%v\n%v", tsA, tsB) + + default: + panic(fmt.Sprintf("invalid csr key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/csr/simulation/decoder_test.go b/x/csr/simulation/decoder_test.go new file mode 100644 index 00000000..679bdab1 --- /dev/null +++ b/x/csr/simulation/decoder_test.go @@ -0,0 +1,61 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/Canto-Network/Canto/v7/x/csr/keeper" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/evmos/ethermint/tests" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/csr" + "github.com/Canto-Network/Canto/v7/x/csr/simulation" + "github.com/Canto-Network/Canto/v7/x/csr/types" +) + +func TestCsrStore(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(csr.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + csr := types.CSR{ + Id: 1, + Contracts: []string{tests.GenerateAddress().Hex()}, + } + + nftId := uint64(1) + + turnstile := tests.GenerateAddress() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefixCSR, Value: cdc.MustMarshal(&csr)}, + {Key: types.KeyPrefixContract, Value: keeper.UInt64ToBytes(nftId)}, + {Key: types.KeyPrefixAddrs, Value: turnstile.Bytes()}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CSR", fmt.Sprintf("%v\n%v", csr, csr)}, + {"NFTId", fmt.Sprintf("%v\n%v", nftId, nftId)}, + {"Turnstile", fmt.Sprintf("%v\n%v", turnstile, turnstile)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/csr/simulation/genesis.go b/x/csr/simulation/genesis.go new file mode 100644 index 00000000..3a81c1b4 --- /dev/null +++ b/x/csr/simulation/genesis.go @@ -0,0 +1,48 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/csr/types" +) + +// DONTCOVER + +// simulation parameter constants +const ( + enableCsr = "enable_csr" + csrShares = "csr_shares" +) + +func generateRandomBool(r *rand.Rand) bool { + return r.Int63()%2 == 0 +} + +func generateRandomCsrShares(r *rand.Rand) sdkmath.LegacyDec { + return sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2) +} + +// RandomizedGenState generates a random GenesisState for CSR. +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesis() + + simState.AppParams.GetOrGenerate( + enableCsr, &genesis.Params.EnableCsr, simState.Rand, + func(r *rand.Rand) { genesis.Params.EnableCsr = generateRandomBool(r) }, + ) + + simState.AppParams.GetOrGenerate( + csrShares, &genesis.Params.CsrShares, simState.Rand, + func(r *rand.Rand) { genesis.Params.CsrShares = generateRandomCsrShares(r) }, + ) + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated csr parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/csr/simulation/genesis_test.go b/x/csr/simulation/genesis_test.go new file mode 100644 index 00000000..423c62e5 --- /dev/null +++ b/x/csr/simulation/genesis_test.go @@ -0,0 +1,72 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/csr/simulation" + "github.com/Canto-Network/Canto/v7/x/csr/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + require.Equal(t, true, genState.Params.EnableCsr) + require.Equal(t, sdkmath.LegacyNewDecWithPrec(11, 2), genState.Params.CsrShares) +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} diff --git a/x/csr/simulation/proposals.go b/x/csr/simulation/proposals.go new file mode 100644 index 00000000..36b2f118 --- /dev/null +++ b/x/csr/simulation/proposals.go @@ -0,0 +1,44 @@ +package simulation + +import ( + "math/rand" + + "github.com/Canto-Network/Canto/v7/x/csr/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 50 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateMsgUpdateParams, + ), + } +} + +// SimulateMsgUpdateParams returns a random MsgUpdateParams +func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + params := types.DefaultParams() + params.CsrShares = generateRandomCsrShares(r) + params.EnableCsr = generateRandomBool(r) + + return &types.MsgUpdateParams{ + Authority: authority.String(), + Params: params, + } +} diff --git a/x/csr/simulation/proposals_test.go b/x/csr/simulation/proposals_test.go new file mode 100644 index 00000000..43952a5b --- /dev/null +++ b/x/csr/simulation/proposals_test.go @@ -0,0 +1,42 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/csr/simulation" + "github.com/Canto-Network/Canto/v7/x/csr/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 1, len(weightedProposalMsgs)) + + w0 := weightedProposalMsgs[0] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*types.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Authority) + require.Equal(t, math.LegacyNewDecWithPrec(40, 2), msgUpdateParams.Params.CsrShares) //nolint:staticcheck // we're testing deprecated code here + require.Equal(t, true, msgUpdateParams.Params.EnableCsr) +} From d41595fc802f47511695d685f834edc886717654 Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 16:28:00 +0900 Subject: [PATCH 03/25] feat: simulation for `epochs` module --- x/epochs/simulation/decoder.go | 28 ++++++++ x/epochs/simulation/decoder_test.go | 57 ++++++++++++++++ x/epochs/simulation/genesis.go | 54 +++++++++++++++ x/epochs/simulation/genesis_test.go | 102 ++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 x/epochs/simulation/decoder.go create mode 100644 x/epochs/simulation/decoder_test.go create mode 100644 x/epochs/simulation/genesis.go create mode 100644 x/epochs/simulation/genesis_test.go diff --git a/x/epochs/simulation/decoder.go b/x/epochs/simulation/decoder.go new file mode 100644 index 00000000..970e94de --- /dev/null +++ b/x/epochs/simulation/decoder.go @@ -0,0 +1,28 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/epochs/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.KeyPrefixEpoch): + var eA, eB types.EpochInfo + cdc.MustUnmarshal(kvA.Value, &eA) + cdc.MustUnmarshal(kvA.Value, &eB) + return fmt.Sprintf("%v\n%v", eA, eB) + + default: + panic(fmt.Sprintf("invalid epochs key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/epochs/simulation/decoder_test.go b/x/epochs/simulation/decoder_test.go new file mode 100644 index 00000000..319f0af3 --- /dev/null +++ b/x/epochs/simulation/decoder_test.go @@ -0,0 +1,57 @@ +package simulation_test + +import ( + "fmt" + "testing" + "time" + + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/epochs" + "github.com/Canto-Network/Canto/v7/x/epochs/simulation" + "github.com/Canto-Network/Canto/v7/x/epochs/types" +) + +func TestEpochsStore(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(epochs.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + epoch := types.EpochInfo{ + Identifier: types.DayEpochID, + StartTime: time.Time{}, + Duration: time.Hour * 24, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: time.Time{}, + EpochCountingStarted: false, + } + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefixEpoch, Value: cdc.MustMarshal(&epoch)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Epoch", fmt.Sprintf("%v\n%v", epoch, epoch)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/epochs/simulation/genesis.go b/x/epochs/simulation/genesis.go new file mode 100644 index 00000000..5a55cbe4 --- /dev/null +++ b/x/epochs/simulation/genesis.go @@ -0,0 +1,54 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/Canto-Network/Canto/v7/x/epochs/types" +) + +// DONTCOVER + +// RandomizedGenState generates a random GenesisState for epochs. +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesisState() + + epochs := []types.EpochInfo{ + { + Identifier: types.WeekEpochID, + StartTime: simState.GenTimestamp, + Duration: time.Hour * 24 * 7, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: simState.GenTimestamp, + EpochCountingStarted: false, + }, + { + Identifier: types.DayEpochID, + StartTime: simState.GenTimestamp, + Duration: time.Hour * 24, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: simState.GenTimestamp, + EpochCountingStarted: false, + }, + { + Identifier: types.HourEpochID, + StartTime: simState.GenTimestamp, + Duration: time.Hour * 1, + CurrentEpoch: 0, + CurrentEpochStartHeight: 0, + CurrentEpochStartTime: simState.GenTimestamp, + EpochCountingStarted: false, + }, + } + + genesis.Epochs = epochs + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated epochs parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/epochs/simulation/genesis_test.go b/x/epochs/simulation/genesis_test.go new file mode 100644 index 00000000..c02af2ce --- /dev/null +++ b/x/epochs/simulation/genesis_test.go @@ -0,0 +1,102 @@ +package simulation_test + +import ( + "encoding/json" + "fmt" + "math/rand" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/epochs/simulation" + "github.com/Canto-Network/Canto/v7/x/epochs/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + fmt.Println(genState.Epochs) + require.Equal(t, []types.EpochInfo{ + { + Identifier: "week", + StartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + Duration: 604800000000000, + CurrentEpoch: 0, + CurrentEpochStartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + EpochCountingStarted: false, + CurrentEpochStartHeight: 0, + }, + { + Identifier: "day", + StartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + Duration: 86400000000000, + CurrentEpoch: 0, + CurrentEpochStartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + EpochCountingStarted: false, + CurrentEpochStartHeight: 0, + }, + { + Identifier: "hour", + StartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + Duration: 3600000000000, + CurrentEpoch: 0, + CurrentEpochStartTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + EpochCountingStarted: false, + CurrentEpochStartHeight: 0, + }, + }, genState.Epochs) + +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} From 4f222ea2829a48ab485d97705b6f955a8f62c6be Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 17:10:45 +0900 Subject: [PATCH 04/25] WIP: simulation for `erc20` module --- x/erc20/simulation/decoder.go | 40 +++++++ x/erc20/simulation/decoder_test.go | 53 +++++++++ x/erc20/simulation/genesis.go | 41 +++++++ x/erc20/simulation/genesis_test.go | 74 ++++++++++++ x/erc20/simulation/operation.go | 170 +++++++++++++++++++++++++++ x/erc20/simulation/operation_test.go | 126 ++++++++++++++++++++ x/erc20/types/interfaces.go | 2 + x/erc20/types/msg.go | 5 + x/erc20/types/utils.go | 29 +++++ 9 files changed, 540 insertions(+) create mode 100644 x/erc20/simulation/decoder.go create mode 100644 x/erc20/simulation/decoder_test.go create mode 100644 x/erc20/simulation/genesis.go create mode 100644 x/erc20/simulation/genesis_test.go create mode 100644 x/erc20/simulation/operation.go create mode 100644 x/erc20/simulation/operation_test.go diff --git a/x/erc20/simulation/decoder.go b/x/erc20/simulation/decoder.go new file mode 100644 index 00000000..ff9d530c --- /dev/null +++ b/x/erc20/simulation/decoder.go @@ -0,0 +1,40 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.KeyPrefixTokenPair): + var tpA, tpB types.TokenPair + cdc.MustUnmarshal(kvA.Value, &tpA) + cdc.MustUnmarshal(kvB.Value, &tpB) + return fmt.Sprintf("%v\n%v", tpA, tpB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixTokenPairByERC20): + var tpA, tpB types.TokenPair + cdc.MustUnmarshal(kvA.Value, &tpA) + cdc.MustUnmarshal(kvB.Value, &tpB) + return fmt.Sprintf("%v\n%v", tpA, tpB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixTokenPairByDenom): + var tpA, tpB types.TokenPair + cdc.MustUnmarshal(kvA.Value, &tpA) + cdc.MustUnmarshal(kvB.Value, &tpB) + return fmt.Sprintf("%v\n%v", tpA, tpB) + + default: + panic(fmt.Sprintf("invalid erc20 key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/erc20/simulation/decoder_test.go b/x/erc20/simulation/decoder_test.go new file mode 100644 index 00000000..e051955e --- /dev/null +++ b/x/erc20/simulation/decoder_test.go @@ -0,0 +1,53 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/Canto-Network/Canto/v7/x/erc20" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + testutil "github.com/evmos/ethermint/tests" + + "github.com/Canto-Network/Canto/v7/x/erc20/simulation" + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +func TestERC20Store(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(erc20.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + tokenPair := types.NewTokenPair(testutil.GenerateAddress(), "coin", true, types.OWNER_MODULE) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefixTokenPair, Value: cdc.MustMarshal(&tokenPair)}, + {Key: types.KeyPrefixTokenPairByERC20, Value: cdc.MustMarshal(&tokenPair)}, + {Key: types.KeyPrefixTokenPairByDenom, Value: cdc.MustMarshal(&tokenPair)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"TokenPair", fmt.Sprintf("%v\n%v", tokenPair, tokenPair)}, + {"TokenPairByERC20", fmt.Sprintf("%v\n%v", tokenPair, tokenPair)}, + {"TokenPairByDenom", fmt.Sprintf("%v\n%v", tokenPair, tokenPair)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/erc20/simulation/genesis.go b/x/erc20/simulation/genesis.go new file mode 100644 index 00000000..db72b090 --- /dev/null +++ b/x/erc20/simulation/genesis.go @@ -0,0 +1,41 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +// DONTCOVER + +// simulation parameter constants +const ( + enableErc20 = "enable_erc20" + enableEVMHook = "enable_evm_hook" +) + +func generateRandomBool(r *rand.Rand) bool { + return r.Int63()%2 == 0 +} + +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesisState() + + simState.AppParams.GetOrGenerate( + enableErc20, &genesis.Params.EnableErc20, simState.Rand, + func(r *rand.Rand) { genesis.Params.EnableErc20 = generateRandomBool(r) }, + ) + + simState.AppParams.GetOrGenerate( + enableEVMHook, &genesis.Params.EnableEVMHook, simState.Rand, + func(r *rand.Rand) { genesis.Params.EnableEVMHook = generateRandomBool(r) }, + ) + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated erc20 parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/erc20/simulation/genesis_test.go b/x/erc20/simulation/genesis_test.go new file mode 100644 index 00000000..ca0e9204 --- /dev/null +++ b/x/erc20/simulation/genesis_test.go @@ -0,0 +1,74 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/erc20/simulation" + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + require.Equal(t, []types.TokenPair{}, genState.TokenPairs) + require.Equal(t, true, genState.Params.EnableErc20) + require.Equal(t, true, genState.Params.EnableEVMHook) + +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} diff --git a/x/erc20/simulation/operation.go b/x/erc20/simulation/operation.go new file mode 100644 index 00000000..6ce9f5dc --- /dev/null +++ b/x/erc20/simulation/operation.go @@ -0,0 +1,170 @@ +package simulation + +import ( + "math/big" + "math/rand" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/contracts" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/ethereum/go-ethereum/common" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/Canto-Network/Canto/v7/x/erc20/keeper" + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +// Simulation operation weights constants. +const ( + OpWeightMsgConvertCoin = "op_weight_msg_convert_coin" + OpWeightMsgConvertErc20 = "op_weight_msg_convert_erc20" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simtypes.AppParams, + cdc codec.JSONCodec, + ak types.AccountKeeper, + bk types.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgConvertCoinNativeCoin int + appParams.GetOrGenerate(OpWeightMsgConvertCoin, &weightMsgConvertCoinNativeCoin, nil, func(_ *rand.Rand) { + weightMsgConvertCoinNativeCoin = params.DefaultWeightMsgConvertCoin + }) + + var weightMsgConvertErc20NativeCoin int + appParams.GetOrGenerate(OpWeightMsgConvertErc20, &weightMsgConvertErc20NativeCoin, nil, func(_ *rand.Rand) { + weightMsgConvertErc20NativeCoin = params.DefaultWeightMsgConvertErc20 + }) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgConvertCoinNativeCoin, + SimulateMsgConvertCoin(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgConvertErc20NativeCoin, + SimulateMsgConvertErc20(ak, bk, k), + ), + } +} + +// SimulateMsgConvertCoin generates a MsgConvertCoin with random values for convertCoinNativeCoin +func SimulateMsgConvertCoin(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + pairs := k.GetTokenPairs(ctx) + + if len(pairs) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no pairs available"), nil, nil + } + + // randomly pick one pair + pair := pairs[r.Intn(len(pairs))] + baseDenom := pair.GetDenom() + + // select random account that has coins baseDenom + var simAccount simtypes.Account + var spendable sdk.Coins + skip := true + + for i := 0; i < len(accs); i++ { + simAccount, _ = simtypes.RandomAcc(r, accs) + spendable = bk.SpendableCoins(ctx, simAccount.Address) + if spendable.AmountOf(baseDenom).IsPositive() { + skip = false + break + } + } + + if skip { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no account has coins"), nil, nil + } + + priv, _ := ethsecp256k1.GenerateKey() + address := common.BytesToAddress(priv.PubKey().Address().Bytes()) + + msg := types.NewMsgConvertCoin( + sdk.NewCoin(baseDenom, spendable.AmountOf(baseDenom)), + address, + simAccount.Address, + ) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + CoinsSpentInMsg: spendable, + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + } + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgConvertErc20 generates a MsgConvertErc20 with random values for convertERC20NativeCoin. +func SimulateMsgConvertErc20(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + pairs := k.GetTokenPairs(ctx) + + if len(pairs) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil + } + + // randomly pick one pair + pair := pairs[r.Intn(len(pairs))] + + // select random account that has coins baseDenom + var simAccount simtypes.Account + var erc20Balance *big.Int + erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI + skip := true + + for i := 0; i < len(accs); i++ { + simAccount, _ = simtypes.RandomAcc(r, accs) + erc20Balance = k.BalanceOf(ctx, erc20ABI, pair.GetERC20Contract(), common.BytesToAddress(simAccount.Address.Bytes())) + if erc20Balance.Cmp(big.NewInt(0)) > 0 { + skip = false + break + } + } + + if skip { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + } + + msg := types.NewMsgConvertERC20(sdkmath.NewIntFromBigInt(erc20Balance), simAccount.Address, pair.GetERC20Contract(), common.BytesToAddress(simAccount.Address.Bytes())) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + CoinsSpentInMsg: sdk.NewCoins(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + } + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/erc20/simulation/operation_test.go b/x/erc20/simulation/operation_test.go new file mode 100644 index 00000000..6d26c773 --- /dev/null +++ b/x/erc20/simulation/operation_test.go @@ -0,0 +1,126 @@ +package simulation_test + +import ( + "math/rand" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/Canto-Network/Canto/v7/app" + "github.com/Canto-Network/Canto/v7/x/erc20/simulation" + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +func TestWeightedOperations(t *testing.T) { + canto, ctx := createTestApp(t, false) + cdc := types.ModuleCdc + appParams := make(simtypes.AppParams) + + weightedOps := simulation.WeightedOperations( + appParams, + cdc, + canto.AccountKeeper, + canto.BankKeeper, + canto.Erc20Keeper, + ) + + s := rand.NewSource(2) + r := rand.New(s) + accs := getTestingAccounts(t, r, canto, ctx, 10) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + {params.DefaultWeightMsgConvertCoin, types.ModuleName, types.TypeMsgConvertCoin}, + {params.DefaultWeightMsgConvertErc20, types.ModuleName, types.TypeMsgConvertERC20}, + } + + for i, w := range weightedOps { + opMsg, _, _ := w.Op()(r, canto.BaseApp, ctx, accs, ctx.ChainID()) + require.Equal(t, expected[i].weight, w.Weight()) + require.Equal(t, expected[i].opMsgRoute, opMsg.Route) + require.Equal(t, expected[i].opMsgName, opMsg.Name) + } +} + +func createTestApp(t *testing.T, isCheckTx bool) (*app.Canto, sdk.Context) { + app := app.Setup(isCheckTx, nil) + r := rand.New(rand.NewSource(1)) + + simAccs := simtypes.RandomAccounts(r, 10) + + ctx := app.BaseApp.NewContext(isCheckTx) + validator := getTestingValidator0(t, app, ctx, simAccs) + consAddr, err := validator.GetConsAddr() + require.NoError(t, err) + ctx = ctx.WithBlockHeader(cmtproto.Header{Height: 1, + ChainID: "canto_9001-1", + Time: time.Now().UTC(), + ProposerAddress: consAddr, + }) + return app, ctx +} + +func getTestingAccounts(t *testing.T, r *rand.Rand, app *app.Canto, ctx sdk.Context, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 100_000_000) + initCoins := sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, initAmt), + ) + + // add coins to the accounts + for _, account := range accounts { + acc := app.AccountKeeper.NewAccountWithAddress(ctx, account.Address) + app.AccountKeeper.SetAccount(ctx, acc) + err := fundAccount(app.BankKeeper, ctx, account.Address, initCoins) + require.NoError(t, err) + } + + return accounts +} + +func fundAccount(bk bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error { + if err := bk.MintCoins(ctx, types.ModuleName, coins); err != nil { + return err + } + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins); err != nil { + return err + } + return nil +} + +func getTestingValidator0(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account) stakingtypes.Validator { + commission0 := stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()) + return getTestingValidator(t, app, ctx, accounts, commission0, 0) +} + +func getTestingValidator(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account, commission stakingtypes.Commission, n int) stakingtypes.Validator { + account := accounts[n] + valPubKey := account.PubKey + valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) + validator := testutil.NewValidator(t, valAddr, valPubKey) + validator, err := validator.SetInitialCommission(commission) + require.NoError(t, err) + + validator.DelegatorShares = sdkmath.LegacyNewDec(100) + validator.Tokens = app.StakingKeeper.TokensFromConsensusPower(ctx, 100) + + app.StakingKeeper.SetValidator(ctx, validator) + app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) + + return validator +} diff --git a/x/erc20/types/interfaces.go b/x/erc20/types/interfaces.go index c1f20de9..4c512eb4 100644 --- a/x/erc20/types/interfaces.go +++ b/x/erc20/types/interfaces.go @@ -18,6 +18,7 @@ import ( type AccountKeeper interface { GetModuleAddress(moduleName string) sdk.AccAddress GetSequence(context.Context, sdk.AccAddress) (uint64, error) + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI } // BankKeeper defines the expected interface needed to retrieve account balances. @@ -32,6 +33,7 @@ type BankKeeper interface { SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) HasSupply(ctx context.Context, denom string) bool GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins } // EVMKeeper defines the expected EVM keeper interface used on erc20 diff --git a/x/erc20/types/msg.go b/x/erc20/types/msg.go index 69c201f5..45304fa8 100644 --- a/x/erc20/types/msg.go +++ b/x/erc20/types/msg.go @@ -18,6 +18,11 @@ var ( _ sdk.Msg = &MsgConvertERC20{} ) +const ( + TypeMsgConvertCoin = "convert_coin" + TypeMsgConvertERC20 = "convert_ERC20" +) + // NewMsgConvertCoin creates a new instance of MsgConvertCoin func NewMsgConvertCoin(coin sdk.Coin, receiver common.Address, sender sdk.AccAddress) *MsgConvertCoin { // nolint: interfacer return &MsgConvertCoin{ diff --git a/x/erc20/types/utils.go b/x/erc20/types/utils.go index d3c4e304..d5d59d97 100644 --- a/x/erc20/types/utils.go +++ b/x/erc20/types/utils.go @@ -2,9 +2,11 @@ package types import ( "fmt" + "math/rand" "regexp" "strings" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) @@ -81,3 +83,30 @@ func EqualStringSlice(aliasesA, aliasesB []string) bool { return true } + +func GenRandomCoinMetadata(r *rand.Rand) banktypes.Metadata { + randDescription := simtypes.RandStringOfLength(r, 10) + randTokenBase := "a" + simtypes.RandStringOfLength(r, 4) + randSymbol := strings.ToUpper(simtypes.RandStringOfLength(r, 4)) + + validMetadata := banktypes.Metadata{ + Description: randDescription, + Base: randTokenBase, + // NOTE: Denom units MUST be increasing + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: randTokenBase, + Exponent: 0, + }, + { + Denom: randTokenBase[1:], + Exponent: uint32(18), + }, + }, + Name: randTokenBase, + Symbol: randSymbol, + Display: randTokenBase, + } + + return validMetadata +} From c7742e3a273e58d3707635f9901d3eca0b71e9f6 Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 17:21:15 +0900 Subject: [PATCH 05/25] WIP: simulation for `govshuttle` module --- x/govshuttle/simulation/decoder.go | 29 ++++++++++ x/govshuttle/simulation/decoder_test.go | 49 +++++++++++++++++ x/govshuttle/simulation/genesis.go | 20 +++++++ x/govshuttle/simulation/genesis_test.go | 71 +++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 x/govshuttle/simulation/decoder.go create mode 100644 x/govshuttle/simulation/decoder_test.go create mode 100644 x/govshuttle/simulation/genesis.go create mode 100644 x/govshuttle/simulation/genesis_test.go diff --git a/x/govshuttle/simulation/decoder.go b/x/govshuttle/simulation/decoder.go new file mode 100644 index 00000000..99a179d2 --- /dev/null +++ b/x/govshuttle/simulation/decoder.go @@ -0,0 +1,29 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/ethereum/go-ethereum/common" + + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:4], types.PortKey): + var paA, paB common.Address + paA = common.BytesToAddress(kvA.Value) + paB = common.BytesToAddress(kvB.Value) + return fmt.Sprintf("%v\n%v", paA, paB) + + default: + panic(fmt.Sprintf("invalid govshuttle key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/govshuttle/simulation/decoder_test.go b/x/govshuttle/simulation/decoder_test.go new file mode 100644 index 00000000..253db186 --- /dev/null +++ b/x/govshuttle/simulation/decoder_test.go @@ -0,0 +1,49 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/Canto-Network/Canto/v7/x/govshuttle" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/evmos/ethermint/tests" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/govshuttle/simulation" + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" +) + +func TestGovShuttleStore(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(govshuttle.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + portAddress := tests.GenerateAddress() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.PortKey, Value: portAddress.Bytes()}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"PortAddress", fmt.Sprintf("%v\n%v", portAddress, portAddress)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/govshuttle/simulation/genesis.go b/x/govshuttle/simulation/genesis.go new file mode 100644 index 00000000..8fc016eb --- /dev/null +++ b/x/govshuttle/simulation/genesis.go @@ -0,0 +1,20 @@ +package simulation + +import ( + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" +) + +// DONTCOVER + +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesis() + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated govshuttle parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/govshuttle/simulation/genesis_test.go b/x/govshuttle/simulation/genesis_test.go new file mode 100644 index 00000000..2a333634 --- /dev/null +++ b/x/govshuttle/simulation/genesis_test.go @@ -0,0 +1,71 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/govshuttle/simulation" + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + require.Equal(t, types.Params{}, genState.Params) +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} From adb6bb4d2b63936efdfb18117c07ff1d21229c48 Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 4 Jul 2024 17:37:31 +0900 Subject: [PATCH 06/25] feat: simulation for `inflation` module --- x/erc20/simulation/proposal.go | 100 ++++++++++++++++++ x/erc20/simulation/proposal_test.go | 84 +++++++++++++++ x/inflation/module.go | 6 ++ x/inflation/simulation/decoder.go | 54 ++++++++++ x/inflation/simulation/decoder_test.go | 64 ++++++++++++ x/inflation/simulation/genesis.go | 127 +++++++++++++++++++++++ x/inflation/simulation/genesis_test.go | 88 ++++++++++++++++ x/inflation/simulation/proposals.go | 47 +++++++++ x/inflation/simulation/proposals_test.go | 53 ++++++++++ 9 files changed, 623 insertions(+) create mode 100644 x/erc20/simulation/proposal.go create mode 100644 x/erc20/simulation/proposal_test.go create mode 100644 x/inflation/simulation/decoder.go create mode 100644 x/inflation/simulation/decoder_test.go create mode 100644 x/inflation/simulation/genesis.go create mode 100644 x/inflation/simulation/genesis_test.go create mode 100644 x/inflation/simulation/proposals.go create mode 100644 x/inflation/simulation/proposals_test.go diff --git a/x/erc20/simulation/proposal.go b/x/erc20/simulation/proposal.go new file mode 100644 index 00000000..75d34fb1 --- /dev/null +++ b/x/erc20/simulation/proposal.go @@ -0,0 +1,100 @@ +package simulation + +import ( + "math/rand" + + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/Canto-Network/Canto/v7/x/erc20/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" + OpWeightSimulateRegisterCoinProposal = "op_weight_register_coin_proposal" + OpWeightSimulateRegisterERC20Proposal = "op_weight_register_erc20_proposal" + OpWeightSimulateToggleTokenConversionProposal = "op_weight_toggle_token_conversion_proposal" +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateMsgUpdateParams, + ), + simulation.NewWeightedProposalMsg( + OpWeightSimulateRegisterCoinProposal, + params.DefaultWeightRegisterCoinProposal, + SimulateMsgRegisterCoin, + ), + simulation.NewWeightedProposalMsg( + OpWeightSimulateRegisterERC20Proposal, + params.DefaultWeightRegisterERC20Proposal, + SimulateMsgRegisterERC20, + ), + simulation.NewWeightedProposalMsg( + OpWeightSimulateToggleTokenConversionProposal, + params.DefaultWeightToggleTokenConversionProposal, + SimulateMsgToggleTokenConversion, + ), + } +} + +// SimulateMsgUpdateParams returns a random MsgUpdateParams +func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + params := types.DefaultParams() + + params.EnableErc20 = generateRandomBool(r) + params.EnableEVMHook = generateRandomBool(r) + + return &types.MsgUpdateParams{ + Authority: authority.String(), + Params: params, + } +} + +func SimulateMsgRegisterCoin(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + return &types.MsgRegisterCoin{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Metadata: types.GenRandomCoinMetadata(r), + } +} + +func SimulateMsgRegisterERC20(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + return &types.MsgRegisterERC20{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Erc20Address: "", + } +} + +func SimulateMsgToggleTokenConversion(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + return &types.MsgToggleTokenConversion{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Token: "", + } +} diff --git a/x/erc20/simulation/proposal_test.go b/x/erc20/simulation/proposal_test.go new file mode 100644 index 00000000..2b9021bd --- /dev/null +++ b/x/erc20/simulation/proposal_test.go @@ -0,0 +1,84 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/Canto-Network/Canto/v7/x/erc20/simulation" + "github.com/Canto-Network/Canto/v7/x/erc20/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 4, len(weightedProposalMsgs)) + + w0 := weightedProposalMsgs[0] + w1 := weightedProposalMsgs[1] + w2 := weightedProposalMsgs[2] + w3 := weightedProposalMsgs[3] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) + + // tests w1 interface: + require.Equal(t, simulation.OpWeightSimulateRegisterCoinProposal, w1.AppParamsKey()) + require.Equal(t, params.DefaultWeightRegisterCoinProposal, w1.DefaultWeight()) + + // tests w2 interface: + require.Equal(t, simulation.OpWeightSimulateRegisterERC20Proposal, w2.AppParamsKey()) + require.Equal(t, params.DefaultWeightRegisterERC20Proposal, w2.DefaultWeight()) + + // tests w3 interface: + require.Equal(t, simulation.OpWeightSimulateToggleTokenConversionProposal, w3.AppParamsKey()) + require.Equal(t, params.DefaultWeightToggleTokenConversionProposal, w3.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*types.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Authority) + require.Equal(t, true, msgUpdateParams.Params.EnableErc20) + require.Equal(t, true, msgUpdateParams.Params.EnableEVMHook) + + msg = w1.MsgSimulatorFn()(r, ctx, accounts) + msgRegisterCoin, ok := msg.(*types.MsgRegisterCoin) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgRegisterCoin.Authority) + require.NotNil(t, msgRegisterCoin.Title) + require.NotNil(t, msgRegisterCoin.Description) + require.NotNil(t, msgRegisterCoin.Metadata) + + msg = w2.MsgSimulatorFn()(r, ctx, accounts) + msgRegisterERC20, ok := msg.(*types.MsgRegisterERC20) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgRegisterERC20.Authority) + require.NotNil(t, msgRegisterERC20.Title) + require.NotNil(t, msgRegisterERC20.Description) + require.NotNil(t, msgRegisterERC20.Erc20Address) + + msg = w3.MsgSimulatorFn()(r, ctx, accounts) + msgToggleTokenConversion, ok := msg.(*types.MsgToggleTokenConversion) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgToggleTokenConversion.Authority) + require.NotNil(t, msgToggleTokenConversion.Title) + require.NotNil(t, msgToggleTokenConversion.Description) + require.NotNil(t, msgToggleTokenConversion.Token) +} diff --git a/x/inflation/module.go b/x/inflation/module.go index 09012567..ffe593e6 100644 --- a/x/inflation/module.go +++ b/x/inflation/module.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" + "github.com/Canto-Network/Canto/v7/x/inflation/simulation" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -172,6 +173,11 @@ func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes return []simtypes.WeightedProposalContent{} } +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} + // RandomizedParams creates randomized inflation param changes for the simulator. func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { return []simtypes.LegacyParamChange{} diff --git a/x/inflation/simulation/decoder.go b/x/inflation/simulation/decoder.go new file mode 100644 index 00000000..0b54a1c8 --- /dev/null +++ b/x/inflation/simulation/decoder.go @@ -0,0 +1,54 @@ +package simulation + +import ( + "bytes" + "fmt" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/inflation/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding farming type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.KeyPrefixPeriod): + var pA, pB uint64 + pA = sdk.BigEndianToUint64(kvA.Value) + pB = sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("%v\n%v", pA, pB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixEpochMintProvision): + var empA, empB sdkmath.LegacyDec + empA.Unmarshal(kvA.Value) + empB.Unmarshal(kvB.Value) + return fmt.Sprintf("%v\n%v", empA, empB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixEpochIdentifier): + var eiA, eiB string + eiA = string(kvA.Value) + eiB = string(kvB.Value) + return fmt.Sprintf("%v\n%v", eiA, eiB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixEpochsPerPeriod): + var eppA, eppB uint64 + eppA = sdk.BigEndianToUint64(kvA.Value) + eppB = sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("%v\n%v", eppA, eppB) + + case bytes.Equal(kvA.Key[:1], types.KeyPrefixSkippedEpochs): + var seA, seB uint64 + seA = sdk.BigEndianToUint64(kvA.Value) + seB = sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("%v\n%v", seA, seB) + + default: + panic(fmt.Sprintf("invalid farming key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/inflation/simulation/decoder_test.go b/x/inflation/simulation/decoder_test.go new file mode 100644 index 00000000..684862c1 --- /dev/null +++ b/x/inflation/simulation/decoder_test.go @@ -0,0 +1,64 @@ +package simulation_test + +import ( + "fmt" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/inflation" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/Canto-Network/Canto/v7/x/inflation/simulation" + "github.com/Canto-Network/Canto/v7/x/inflation/types" +) + +func TestInflationStore(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(inflation.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + period := uint64(1) + epochMintProvision := sdkmath.LegacyNewDec(2) + epochIdentifier := "epochIdentifier" + epochPerPeriod := uint64(3) + skippedEpoch := uint64(4) + + marshaled, _ := epochMintProvision.Marshal() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.KeyPrefixPeriod, Value: sdk.Uint64ToBigEndian(period)}, + {Key: types.KeyPrefixEpochMintProvision, Value: marshaled}, + {Key: types.KeyPrefixEpochIdentifier, Value: []byte(epochIdentifier)}, + {Key: types.KeyPrefixEpochsPerPeriod, Value: sdk.Uint64ToBigEndian(epochPerPeriod)}, + {Key: types.KeyPrefixSkippedEpochs, Value: sdk.Uint64ToBigEndian(skippedEpoch)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Period", fmt.Sprintf("%v\n%v", period, period)}, + {"EpochMintProvision", fmt.Sprintf("%v\n%v", epochMintProvision, epochMintProvision)}, + {"EpochIdentifier", fmt.Sprintf("%v\n%v", epochIdentifier, epochIdentifier)}, + {"EpochsPerPeriod", fmt.Sprintf("%v\n%v", epochPerPeriod, epochPerPeriod)}, + {"SkippedEpochs", fmt.Sprintf("%v\n%v", skippedEpoch, skippedEpoch)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/inflation/simulation/genesis.go b/x/inflation/simulation/genesis.go new file mode 100644 index 00000000..f90fd085 --- /dev/null +++ b/x/inflation/simulation/genesis.go @@ -0,0 +1,127 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/inflation/types" +) + +// DONTCOVER + +// simulation parameter constants +const ( + mintDenom = "mint_denom" + exponentialCalculation = "exponential_calculation" + inflationDistribution = "inflation_distribution" + enableInflation = "enable_inflation" + period = "period" + epochIdentifier = "epoch_identifier" + epochsPerPeriod = "epochs_per_period" + skippedEpochs = "skipped_epochs" +) + +func generateRandomBool(r *rand.Rand) bool { + return r.Int63()%2 == 0 +} + +func generateMintDenom(r *rand.Rand) string { + return sdk.DefaultBondDenom +} + +func generateExponentialCalculation(r *rand.Rand) types.ExponentialCalculation { + return types.ExponentialCalculation{ + A: sdkmath.LegacyNewDec(int64(simtypes.RandIntBetween(r, 0, 10000000))), + R: sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2), + C: sdkmath.LegacyZeroDec(), + BondingTarget: sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 100)), 2), + MaxVariance: sdkmath.LegacyZeroDec(), + } +} + +func generateInflationDistribution(r *rand.Rand) types.InflationDistribution { + + stakingRewards := sdkmath.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2) + communityPool := sdkmath.LegacyNewDec(1).Sub(stakingRewards) + + return types.InflationDistribution{ + StakingRewards: stakingRewards, + CommunityPool: communityPool, + } +} + +func generateEnableInflation(r *rand.Rand) bool { + return generateRandomBool(r) +} + +func generatePeriod(r *rand.Rand) uint64 { + return uint64(simtypes.RandIntBetween(r, 0, 10000000)) +} + +func generateEpochIdentifier(r *rand.Rand) string { + return "day" +} + +func generateEpochsPerPeriod(r *rand.Rand) int64 { + return int64(simtypes.RandIntBetween(r, 0, 10000000)) +} + +func generateSkippedEpochs(r *rand.Rand) uint64 { + return uint64(simtypes.RandIntBetween(r, 0, 10000000)) +} + +// RandomizedGenState generates a random GenesisState for inflation. + +func RandomizedGenState(simState *module.SimulationState) { + genesis := types.DefaultGenesisState() + + simState.AppParams.GetOrGenerate( + mintDenom, &genesis.Params.MintDenom, simState.Rand, + func(r *rand.Rand) { genesis.Params.MintDenom = generateMintDenom(r) }, + ) + + simState.AppParams.GetOrGenerate( + exponentialCalculation, &genesis.Params.ExponentialCalculation, simState.Rand, + func(r *rand.Rand) { genesis.Params.ExponentialCalculation = generateExponentialCalculation(r) }, + ) + + simState.AppParams.GetOrGenerate( + inflationDistribution, &genesis.Params.InflationDistribution, simState.Rand, + func(r *rand.Rand) { genesis.Params.InflationDistribution = generateInflationDistribution(r) }, + ) + + simState.AppParams.GetOrGenerate( + enableInflation, &genesis.Params.EnableInflation, simState.Rand, + func(r *rand.Rand) { genesis.Params.EnableInflation = generateEnableInflation(r) }, + ) + + simState.AppParams.GetOrGenerate( + period, &genesis.Period, simState.Rand, + func(r *rand.Rand) { genesis.Period = generatePeriod(r) }, + ) + + simState.AppParams.GetOrGenerate( + epochIdentifier, &genesis.EpochIdentifier, simState.Rand, + func(r *rand.Rand) { genesis.EpochIdentifier = generateEpochIdentifier(r) }, + ) + + simState.AppParams.GetOrGenerate( + epochsPerPeriod, &genesis.EpochsPerPeriod, simState.Rand, + func(r *rand.Rand) { genesis.EpochsPerPeriod = generateEpochsPerPeriod(r) }, + ) + + simState.AppParams.GetOrGenerate( + skippedEpochs, &genesis.SkippedEpochs, simState.Rand, + func(r *rand.Rand) { genesis.SkippedEpochs = generateSkippedEpochs(r) }, + ) + + bz, _ := json.MarshalIndent(&genesis, "", " ") + fmt.Printf("Selected randomly generated inflation parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/inflation/simulation/genesis_test.go b/x/inflation/simulation/genesis_test.go new file mode 100644 index 00000000..96394285 --- /dev/null +++ b/x/inflation/simulation/genesis_test.go @@ -0,0 +1,88 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/Canto-Network/Canto/v7/x/inflation/simulation" + "github.com/Canto-Network/Canto/v7/x/inflation/types" +) + +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(2) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var genState types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &genState) + + require.Equal(t, "stake", genState.Params.MintDenom) + require.Equal(t, types.ExponentialCalculation{ + A: sdkmath.LegacyNewDec(2712964), + R: sdkmath.LegacyNewDecWithPrec(11, 2), + C: sdkmath.LegacyZeroDec(), + BondingTarget: sdkmath.LegacyNewDecWithPrec(94, 2), + MaxVariance: sdkmath.LegacyZeroDec(), + }, genState.Params.ExponentialCalculation) + require.Equal(t, types.InflationDistribution{ + StakingRewards: sdkmath.LegacyNewDecWithPrec(1, 1), + CommunityPool: sdkmath.LegacyNewDecWithPrec(9, 1), + }, genState.Params.InflationDistribution) + require.Equal(t, false, genState.Params.EnableInflation) + require.Equal(t, uint64(1654145), genState.Period) + require.Equal(t, "day", genState.EpochIdentifier) + require.Equal(t, int64(6634432), genState.EpochsPerPeriod) + require.Equal(t, uint64(5142676), genState.SkippedEpochs) + +} + +// TestInvalidGenesisState tests invalid genesis states. +func TestInvalidGenesisState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} diff --git a/x/inflation/simulation/proposals.go b/x/inflation/simulation/proposals.go new file mode 100644 index 00000000..a9fd7b3e --- /dev/null +++ b/x/inflation/simulation/proposals.go @@ -0,0 +1,47 @@ +package simulation + +import ( + "math/rand" + + "github.com/Canto-Network/Canto/v7/x/inflation/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateMsgUpdateParams, + ), + } +} + +// SimulateMsgUpdateParams returns a random MsgUpdateParams +func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + params := types.DefaultParams() + + params.MintDenom = generateMintDenom(r) + params.ExponentialCalculation = generateExponentialCalculation(r) + params.InflationDistribution = generateInflationDistribution(r) + params.EnableInflation = generateRandomBool(r) + + return &types.MsgUpdateParams{ + Authority: authority.String(), + Params: params, + } +} diff --git a/x/inflation/simulation/proposals_test.go b/x/inflation/simulation/proposals_test.go new file mode 100644 index 00000000..ae1b03ce --- /dev/null +++ b/x/inflation/simulation/proposals_test.go @@ -0,0 +1,53 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/x/inflation/simulation" + "github.com/Canto-Network/Canto/v7/x/inflation/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 1, len(weightedProposalMsgs)) + + w0 := weightedProposalMsgs[0] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*types.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Authority) + require.Equal(t, sdk.DefaultBondDenom, msgUpdateParams.Params.MintDenom) //nolint:staticcheck // we're testing deprecated code here + require.Equal(t, types.ExponentialCalculation{ + A: sdkmath.LegacyNewDec(6122540), + R: sdkmath.LegacyNewDecWithPrec(56, 2), + C: sdkmath.LegacyZeroDec(), + BondingTarget: sdkmath.LegacyNewDecWithPrec(7, 2), + MaxVariance: sdkmath.LegacyZeroDec(), + }, msgUpdateParams.Params.ExponentialCalculation) + require.Equal(t, types.InflationDistribution{ + StakingRewards: sdkmath.LegacyNewDecWithPrec(94, 2), + CommunityPool: sdkmath.LegacyNewDecWithPrec(6, 2), + }, msgUpdateParams.Params.InflationDistribution) + require.Equal(t, false, msgUpdateParams.Params.EnableInflation) +} From 25d9a53a5e6021a430b793500b5e235cd4022329 Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 5 Jul 2024 16:44:35 +0900 Subject: [PATCH 07/25] feat: proposal simulation for `erc20` module --- app/app.go | 2 +- x/erc20/keeper/evm.go | 4 +- x/erc20/keeper/test_heleprs.go | 119 +++++++++++++++ x/erc20/module.go | 18 ++- x/erc20/simulation/operation_test.go | 1 + x/erc20/simulation/proposal.go | 212 +++++++++++++++++++++++---- x/erc20/simulation/proposal_test.go | 11 +- x/erc20/types/interfaces.go | 15 ++ 8 files changed, 344 insertions(+), 38 deletions(-) create mode 100644 x/erc20/keeper/test_heleprs.go diff --git a/app/app.go b/app/app.go index ce44812f..fa21865b 100644 --- a/app/app.go +++ b/app/app.go @@ -764,7 +764,7 @@ func NewCanto( // Canto app modules inflation.NewAppModule(app.InflationKeeper, app.AccountKeeper, *app.StakingKeeper), - erc20.NewAppModule(app.Erc20Keeper, app.AccountKeeper), + erc20.NewAppModule(app.Erc20Keeper, app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.FeeMarketKeeper), epochs.NewAppModule(appCodec, app.EpochsKeeper), onboarding.NewAppModule(*app.OnboardingKeeper), govshuttle.NewAppModule(app.GovshuttleKeeper, app.AccountKeeper), diff --git a/x/erc20/keeper/evm.go b/x/erc20/keeper/evm.go index e884683b..48cca555 100644 --- a/x/erc20/keeper/evm.go +++ b/x/erc20/keeper/evm.go @@ -5,8 +5,8 @@ import ( "math/big" errorsmod "cosmossdk.io/errors" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -183,7 +183,7 @@ func (k Keeper) CallEVMWithData( return nil, errorsmod.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal tx args: %s", err.Error()) } - gasRes, err := k.evmKeeper.EstimateGas(sdk.WrapSDKContext(ctx), &evmtypes.EthCallRequest{ + gasRes, err := k.evmKeeper.EstimateGas(ctx, &evmtypes.EthCallRequest{ Args: args, GasCap: config.DefaultGasCap, }) diff --git a/x/erc20/keeper/test_heleprs.go b/x/erc20/keeper/test_heleprs.go new file mode 100644 index 00000000..0a5c33b6 --- /dev/null +++ b/x/erc20/keeper/test_heleprs.go @@ -0,0 +1,119 @@ +package keeper + +import ( + "encoding/json" + "fmt" + "math/big" + + errorsmod "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/ethermint/server/config" + evm "github.com/evmos/ethermint/x/evm/types" + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" + + "github.com/Canto-Network/Canto/v7/contracts" + "github.com/Canto-Network/Canto/v7/x/erc20/types" +) + +func DeployContract(ctx sdk.Context, + evmKeeper types.EVMKeeper, feemarketKeeper types.FeeMarketKeeper, + address common.Address, signer keyring.Signer, + name, symbol string, decimals uint8) (common.Address, error) { + chainID := evmKeeper.ChainID() + + ctorArgs, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack("", name, symbol, decimals) + if err != nil { + return common.Address{}, err + } + + data := append(contracts.ERC20MinterBurnerDecimalsContract.Bin, ctorArgs...) + args, err := json.Marshal(&evm.TransactionArgs{ + From: &address, + Data: (*hexutil.Bytes)(&data), + }) + if err != nil { + return common.Address{}, err + } + + res, err := evmKeeper.EstimateGas(ctx, &evm.EthCallRequest{ + Args: args, + GasCap: uint64(config.DefaultGasCap), + }) + if err != nil { + return common.Address{}, err + } + + nonce := evmKeeper.GetNonce(ctx, address) + erc20DeployTx := evm.NewTxContract( + chainID, + nonce, + nil, // amount + res.Gas, // gasLimit + nil, // gasPrice + feemarkettypes.DefaultParams().BaseFee.BigInt(), + big.NewInt(1), + data, // input + ðtypes.AccessList{}, // accesses + ) + + erc20DeployTx.From = address.Hex() + err = erc20DeployTx.Sign(ethtypes.LatestSignerForChainID(chainID), signer) + if err != nil { + return common.Address{}, err + } + + rsp, err := evmKeeper.EthereumTx(ctx, erc20DeployTx) + if err != nil { + return common.Address{}, err + } + + if rsp.VmError != "" { + return common.Address{}, fmt.Errorf("failed to deploy contract: %s", rsp.VmError) + } + return crypto.CreateAddress(address, nonce), nil +} + +func DeployERC20Contract( + ctx sdk.Context, + k Keeper, + ak types.AccountKeeper, + coinMetadata banktypes.Metadata, +) (common.Address, error) { + decimals := uint8(0) + if len(coinMetadata.DenomUnits) > 0 { + decimalsIdx := len(coinMetadata.DenomUnits) - 1 + decimals = uint8(coinMetadata.DenomUnits[decimalsIdx].Exponent) + } + ctorArgs, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack( + "", + coinMetadata.Name, + coinMetadata.Symbol, + decimals, + ) + if err != nil { + return common.Address{}, errorsmod.Wrapf(types.ErrABIPack, "coin metadata is invalid %s: %s", coinMetadata.Name, err.Error()) + } + + data := make([]byte, len(contracts.ERC20MinterBurnerDecimalsContract.Bin)+len(ctorArgs)) + copy(data[:len(contracts.ERC20MinterBurnerDecimalsContract.Bin)], contracts.ERC20MinterBurnerDecimalsContract.Bin) + copy(data[len(contracts.ERC20MinterBurnerDecimalsContract.Bin):], ctorArgs) + + nonce, err := ak.GetSequence(ctx, types.ModuleAddress.Bytes()) + if err != nil { + return common.Address{}, err + } + + contractAddr := crypto.CreateAddress(types.ModuleAddress, nonce) + _, err = k.CallEVMWithData(ctx, types.ModuleAddress, nil, data, true) + if err != nil { + return common.Address{}, errorsmod.Wrapf(err, "failed to deploy contract for %s", coinMetadata.Name) + } + + return contractAddr, nil +} diff --git a/x/erc20/module.go b/x/erc20/module.go index 468f315a..01d304a3 100644 --- a/x/erc20/module.go +++ b/x/erc20/module.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" + "github.com/Canto-Network/Canto/v7/x/erc20/simulation" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -85,18 +86,28 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic keeper keeper.Keeper - ak authkeeper.AccountKeeper + // TODO: Add keepers that your application module simulation requires + ak authkeeper.AccountKeeper + bk types.BankKeeper + ek types.EVMKeeper + fk types.FeeMarketKeeper } // NewAppModule creates a new AppModule Object func NewAppModule( k keeper.Keeper, ak authkeeper.AccountKeeper, + bk types.BankKeeper, + ek types.EVMKeeper, + fk types.FeeMarketKeeper, ) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: k, ak: ak, + bk: bk, + ek: ek, + fk: fk, } } @@ -127,6 +138,11 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { } } +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs(am.keeper, am.ak, am.bk, am.ek, am.fk) +} + func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { var genesisState types.GenesisState diff --git a/x/erc20/simulation/operation_test.go b/x/erc20/simulation/operation_test.go index 6d26c773..978aafd2 100644 --- a/x/erc20/simulation/operation_test.go +++ b/x/erc20/simulation/operation_test.go @@ -71,6 +71,7 @@ func createTestApp(t *testing.T, isCheckTx bool) (*app.Canto, sdk.Context) { Time: time.Now().UTC(), ProposerAddress: consAddr, }) + ctx = ctx.WithChainID("canto_9001-1") return app, ctx } diff --git a/x/erc20/simulation/proposal.go b/x/erc20/simulation/proposal.go index 75d34fb1..8a777304 100644 --- a/x/erc20/simulation/proposal.go +++ b/x/erc20/simulation/proposal.go @@ -2,13 +2,22 @@ package simulation import ( "math/rand" + "strings" + sdkmath "cosmossdk.io/math" "github.com/Canto-Network/Canto/v7/app/params" + "github.com/Canto-Network/Canto/v7/contracts" + "github.com/Canto-Network/Canto/v7/x/erc20/keeper" "github.com/Canto-Network/Canto/v7/x/erc20/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/address" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/ethereum/go-ethereum/common" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" ) // Simulation operation weights constants @@ -19,10 +28,12 @@ const ( OpWeightSimulateRegisterCoinProposal = "op_weight_register_coin_proposal" OpWeightSimulateRegisterERC20Proposal = "op_weight_register_erc20_proposal" OpWeightSimulateToggleTokenConversionProposal = "op_weight_toggle_token_conversion_proposal" + + erc20Decimals = uint8(18) ) // ProposalMsgs defines the module weighted proposals' contents -func ProposalMsgs() []simtypes.WeightedProposalMsg { +func ProposalMsgs(k keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper, ek types.EVMKeeper, fk types.FeeMarketKeeper) []simtypes.WeightedProposalMsg { return []simtypes.WeightedProposalMsg{ simulation.NewWeightedProposalMsg( OpWeightMsgUpdateParams, @@ -32,17 +43,17 @@ func ProposalMsgs() []simtypes.WeightedProposalMsg { simulation.NewWeightedProposalMsg( OpWeightSimulateRegisterCoinProposal, params.DefaultWeightRegisterCoinProposal, - SimulateMsgRegisterCoin, + SimulateMsgRegisterCoin(k, bk), ), simulation.NewWeightedProposalMsg( OpWeightSimulateRegisterERC20Proposal, params.DefaultWeightRegisterERC20Proposal, - SimulateMsgRegisterERC20, + SimulateMsgRegisterERC20(k, ak, bk, ek, fk), ), simulation.NewWeightedProposalMsg( OpWeightSimulateToggleTokenConversionProposal, params.DefaultWeightToggleTokenConversionProposal, - SimulateMsgToggleTokenConversion, + SimulateMsgToggleTokenConversion(k, bk, ek, fk), ), } } @@ -63,38 +74,183 @@ func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) } } -func SimulateMsgRegisterCoin(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") +func SimulateMsgRegisterCoin(k keeper.Keeper, bk types.BankKeeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { + coinMetadata := types.GenRandomCoinMetadata(r) + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1)))); err != nil { + panic(err) + } + bankparams := bk.GetParams(ctx) + bankparams.DefaultSendEnabled = true + bk.SetParams(ctx, bankparams) + + params := k.GetParams(ctx) + params.EnableErc20 = true + k.SetParams(ctx, params) + + // mint cosmos coin to random accounts + randomIteration := r.Intn(10) + for i := 0; i < randomIteration; i++ { + simAccount, _ := simtypes.RandomAcc(r, accs) + + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + panic(err) + } + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + panic(err) + } + } + + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + msg := &types.MsgRegisterCoin{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Metadata: coinMetadata, + } + + if _, err := k.RegisterCoinProposal(ctx, msg); err != nil { + panic(err) + } - return &types.MsgRegisterCoin{ - Authority: authority.String(), - Title: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 100), - Metadata: types.GenRandomCoinMetadata(r), + return msg } } -func SimulateMsgRegisterERC20(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") +func SimulateMsgRegisterERC20(k keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, evmKeeper types.EVMKeeper, feemarketKeeper types.FeeMarketKeeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { // use the default gov module account address as authority + params := k.GetParams(ctx) + params.EnableErc20 = true + k.SetParams(ctx, params) + + evmParams := evmtypes.DefaultParams() + evmParams.EvmDenom = "stake" + evmKeeper.SetParams(ctx, evmParams) + + isNativeErc20 := r.Intn(2) == 1 + // account key + priv, err := ethsecp256k1.GenerateKey() + if err != nil { + panic(err) + } + addr := common.BytesToAddress(priv.PubKey().Address().Bytes()) + signer := tests.NewSigner(priv) + + erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI + + var deployer common.Address + var contractAddr common.Address + coinMetadata := types.GenRandomCoinMetadata(r) + + coins := sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, sdkmath.NewInt(10000000000000000))) + if err = bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { + panic(err) + } + + if err = bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, coins); err != nil { + panic(err) + } + if isNativeErc20 { + deployer = types.ModuleAddress + contractAddr, err = keeper.DeployERC20Contract(ctx, k, accountKeeper, coinMetadata) + } else { + deployer = addr + erc20Name := coinMetadata.Name + erc20Symbol := coinMetadata.Symbol + contractAddr, err = keeper.DeployContract(ctx, evmKeeper, feemarketKeeper, deployer, signer, erc20Name, erc20Symbol, erc20Decimals) + } - return &types.MsgRegisterERC20{ - Authority: authority.String(), - Title: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 100), - Erc20Address: "", + // mint cosmos coin to random accounts + randomIteration := r.Intn(10) + for i := 0; i < randomIteration; i++ { + simAccount, _ := simtypes.RandomAcc(r, accs) + + mintAmt := sdkmath.NewInt(100000000) + receiver := common.BytesToAddress(simAccount.Address.Bytes()) + before := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + _, err = k.CallEVM(ctx, erc20ABI, deployer, contractAddr, true, "mint", receiver, mintAmt.BigInt()) + if err != nil { + panic(err) + } + after := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + if after.Cmp(before.Add(before, mintAmt.BigInt())) != 0 { + panic("mint failed") + } + } + + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") + + msg := &types.MsgRegisterERC20{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Erc20Address: contractAddr.String(), + } + + if _, err := k.RegisterERC20Proposal(ctx, msg); err != nil { + panic(err) + } + + return msg } } -func SimulateMsgToggleTokenConversion(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") +func SimulateMsgToggleTokenConversion(k keeper.Keeper, bankKeeper types.BankKeeper, evmKeeper types.EVMKeeper, feemarketKeeper types.FeeMarketKeeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { + params := k.GetParams(ctx) + params.EnableErc20 = true + k.SetParams(ctx, params) + + evmParams := evmtypes.DefaultParams() + evmParams.EvmDenom = "stake" + evmKeeper.SetParams(ctx, evmParams) + + // account key + priv, err := ethsecp256k1.GenerateKey() + if err != nil { + panic(err) + } + addr := common.BytesToAddress(priv.PubKey().Address().Bytes()) + signer := tests.NewSigner(priv) + + erc20Name := simtypes.RandStringOfLength(r, 10) + erc20Symbol := strings.ToUpper(simtypes.RandStringOfLength(r, 4)) + + coins := sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, sdkmath.NewInt(10000000000000000))) + if err = bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { + panic(err) + } + + if err = bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, coins); err != nil { + panic(err) + } + + contractAddr, err := keeper.DeployContract(ctx, evmKeeper, feemarketKeeper, addr, signer, erc20Name, erc20Symbol, erc20Decimals) + if err != nil { + panic(err) + } + + _, err = k.RegisterERC20(ctx, contractAddr) + if err != nil { + panic(err) + } + + var authority sdk.AccAddress = address.Module("gov") + + msg := &types.MsgToggleTokenConversion{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Token: contractAddr.String(), + } + + if _, err := k.ToggleTokenConversionProposal(ctx, msg); err != nil { + panic(err) + } - return &types.MsgToggleTokenConversion{ - Authority: authority.String(), - Title: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 100), - Token: "", + return msg } } diff --git a/x/erc20/simulation/proposal_test.go b/x/erc20/simulation/proposal_test.go index 2b9021bd..b6eafe00 100644 --- a/x/erc20/simulation/proposal_test.go +++ b/x/erc20/simulation/proposal_test.go @@ -7,23 +7,22 @@ import ( "github.com/Canto-Network/Canto/v7/app/params" "github.com/Canto-Network/Canto/v7/x/erc20/simulation" "github.com/Canto-Network/Canto/v7/x/erc20/types" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/address" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/stretchr/testify/require" ) func TestProposalMsgs(t *testing.T) { + app, ctx := createTestApp(t, false) + // initialize parameters - s := rand.NewSource(1) + s := rand.NewSource(2) r := rand.New(s) - ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) - accounts := simtypes.RandomAccounts(r, 3) + accounts := getTestingAccounts(t, r, app, ctx, 10) // execute ProposalMsgs function - weightedProposalMsgs := simulation.ProposalMsgs() + weightedProposalMsgs := simulation.ProposalMsgs(app.Erc20Keeper, app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.FeeMarketKeeper) require.Equal(t, 4, len(weightedProposalMsgs)) w0 := weightedProposalMsgs[0] diff --git a/x/erc20/types/interfaces.go b/x/erc20/types/interfaces.go index 4c512eb4..c5de19de 100644 --- a/x/erc20/types/interfaces.go +++ b/x/erc20/types/interfaces.go @@ -2,9 +2,11 @@ package types import ( "context" + "math/big" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -25,6 +27,7 @@ type AccountKeeper interface { type BankKeeper interface { SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error IsSendEnabledCoin(ctx context.Context, coin sdk.Coin) bool @@ -34,12 +37,24 @@ type BankKeeper interface { HasSupply(ctx context.Context, denom string) bool GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + GetParams(ctx context.Context) banktypes.Params + SetParams(ctx context.Context, params banktypes.Params) error } // EVMKeeper defines the expected EVM keeper interface used on erc20 type EVMKeeper interface { GetParams(ctx sdk.Context) evmtypes.Params + SetParams(ctx sdk.Context, params evmtypes.Params) error GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) + ChainID() *big.Int + GetNonce(ctx sdk.Context, addr common.Address) uint64 + EthereumTx(goCtx context.Context, msg *evmtypes.MsgEthereumTx) (*evmtypes.MsgEthereumTxResponse, error) +} + +type FeeMarketKeeper interface { + GetBaseFee(ctx sdk.Context) *big.Int + GetParams(ctx sdk.Context) (params feemarkettypes.Params) + SetParams(ctx sdk.Context, params feemarkettypes.Params) error } From e4cf1a82f06fab7b03b57af6e30671b38e68358a Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 5 Jul 2024 17:46:22 +0900 Subject: [PATCH 08/25] feat: proposal simulation for `govshuttle` module --- x/govshuttle/module.go | 8 ++ x/govshuttle/simulation/proposals.go | 97 +++++++++++++++++++++ x/govshuttle/simulation/proposals_test.go | 100 ++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 x/govshuttle/simulation/proposals.go create mode 100644 x/govshuttle/simulation/proposals_test.go diff --git a/x/govshuttle/module.go b/x/govshuttle/module.go index 37bcf9e1..a774c162 100644 --- a/x/govshuttle/module.go +++ b/x/govshuttle/module.go @@ -6,6 +6,7 @@ import ( // this line is used by starport scaffolding # 1 + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -13,6 +14,7 @@ import ( "github.com/Canto-Network/Canto/v7/x/govshuttle/client/cli" "github.com/Canto-Network/Canto/v7/x/govshuttle/keeper" + "github.com/Canto-Network/Canto/v7/x/govshuttle/simulation" "github.com/Canto-Network/Canto/v7/x/govshuttle/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -125,6 +127,7 @@ func (am AppModule) Name() string { // module-specific GRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) } // RegisterInvariants registers the capability module's invariants. @@ -150,3 +153,8 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 2 } + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs(am.keeper) +} diff --git a/x/govshuttle/simulation/proposals.go b/x/govshuttle/simulation/proposals.go new file mode 100644 index 00000000..36ddd124 --- /dev/null +++ b/x/govshuttle/simulation/proposals.go @@ -0,0 +1,97 @@ +package simulation + +import ( + "math/rand" + + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/Canto-Network/Canto/v7/x/govshuttle/keeper" + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + OpWeightSimulateLendingMarketProposal = "op_weight_lending_market_proposal" + OpWeightSimulateTreasuryProposal = "op_weight_treasury_proposal" +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs(k keeper.Keeper) []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightSimulateLendingMarketProposal, + params.DefaultWeightLendingMarketProposal, + SimulateMsgLendingMarket(k), + ), + simulation.NewWeightedProposalMsg( + OpWeightSimulateTreasuryProposal, + params.DefaultWeightRegisterERC20Proposal, + SimulateMsgTreasury(k), + ), + } +} + +func SimulateMsgLendingMarket(k keeper.Keeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { + treasuryProposalMetadata := types.TreasuryProposalMetadata{ + PropID: 1, + Recipient: accs[r.Intn(len(accs))].Address.String(), + Amount: uint64(simtypes.RandIntBetween(r, 0, 10000)), + Denom: "canto", + } + + treasuryProposal := types.TreasuryProposal{ + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Metadata: &treasuryProposalMetadata, + } + + lendingMarketProposal := treasuryProposal.FromTreasuryToLendingMarket() + lendingMarketProposal.Metadata.Calldatas = []string{"callData1"} + + var authority sdk.AccAddress = address.Module("gov") + + msg := &types.MsgLendingMarketProposal{ + Authority: authority.String(), + Title: lendingMarketProposal.Title, + Description: lendingMarketProposal.Description, + Metadata: lendingMarketProposal.Metadata, + } + + if _, err := k.LendingMarketProposal(ctx, msg); err != nil { + panic(err) + } + + return msg + } +} + +func SimulateMsgTreasury(k keeper.Keeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { + + treasuryProposalMetadata := types.TreasuryProposalMetadata{ + PropID: 1, + Recipient: accs[r.Intn(len(accs))].Address.String(), + Amount: uint64(simtypes.RandIntBetween(r, 0, 10000)), + Denom: "canto", + } + + var authority sdk.AccAddress = address.Module("gov") + + msg := &types.MsgTreasuryProposal{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Metadata: &treasuryProposalMetadata, + } + + if _, err := k.TreasuryProposal(ctx, msg); err != nil { + panic(err) + } + + return msg + } +} diff --git a/x/govshuttle/simulation/proposals_test.go b/x/govshuttle/simulation/proposals_test.go new file mode 100644 index 00000000..88b01e7b --- /dev/null +++ b/x/govshuttle/simulation/proposals_test.go @@ -0,0 +1,100 @@ +package simulation_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/Canto-Network/Canto/v7/app/params" + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + "github.com/Canto-Network/Canto/v7/app" + "github.com/Canto-Network/Canto/v7/x/govshuttle/simulation" + "github.com/Canto-Network/Canto/v7/x/govshuttle/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func createTestApp(t *testing.T, isCheckTx bool) (*app.Canto, sdk.Context) { + app := app.Setup(isCheckTx, nil) + r := rand.New(rand.NewSource(1)) + + simAccs := simtypes.RandomAccounts(r, 10) + + ctx := app.BaseApp.NewContext(isCheckTx) + validator := getTestingValidator0(t, app, ctx, simAccs) + consAddr, err := validator.GetConsAddr() + require.NoError(t, err) + ctx = ctx.WithBlockHeader(cmtproto.Header{Height: 1, + ChainID: "canto_9001-1", + Time: time.Now().UTC(), + ProposerAddress: consAddr, + }) + ctx = ctx.WithChainID("canto_9001-1") + return app, ctx +} + +func getTestingValidator0(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account) stakingtypes.Validator { + commission0 := stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()) + return getTestingValidator(t, app, ctx, accounts, commission0, 0) +} + +func getTestingValidator(t *testing.T, app *app.Canto, ctx sdk.Context, accounts []simtypes.Account, commission stakingtypes.Commission, n int) stakingtypes.Validator { + account := accounts[n] + valPubKey := account.PubKey + valAddr := sdk.ValAddress(account.PubKey.Address().Bytes()) + validator := testutil.NewValidator(t, valAddr, valPubKey) + validator, err := validator.SetInitialCommission(commission) + require.NoError(t, err) + + validator.DelegatorShares = sdkmath.LegacyNewDec(100) + validator.Tokens = app.StakingKeeper.TokensFromConsensusPower(ctx, 100) + + app.StakingKeeper.SetValidator(ctx, validator) + app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) + + return validator +} + +func TestProposalMsgs(t *testing.T) { + app, ctx := createTestApp(t, false) + + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs(app.GovshuttleKeeper) + require.Equal(t, 2, len(weightedProposalMsgs)) + + w0 := weightedProposalMsgs[0] + w1 := weightedProposalMsgs[1] + + // tests w0 interface + require.Equal(t, simulation.OpWeightSimulateLendingMarketProposal, w0.AppParamsKey()) + require.Equal(t, params.DefaultWeightLendingMarketProposal, w0.DefaultWeight()) + + // tests w1 interface + require.Equal(t, simulation.OpWeightSimulateTreasuryProposal, w1.AppParamsKey()) + require.Equal(t, params.DefaultWeightTreasuryProposal, w1.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + MsgLendingMarket, ok := msg.(*types.MsgLendingMarketProposal) + require.True(t, ok) + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), MsgLendingMarket.Authority) + + msg = w1.MsgSimulatorFn()(r, ctx, accounts) + MsgTreasury, ok := msg.(*types.MsgTreasuryProposal) + require.True(t, ok) + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), MsgTreasury.Authority) + +} From c9021b28b8ffdd41307fcdd4222ac37681bf6cf3 Mon Sep 17 00:00:00 2001 From: poorphd Date: Mon, 8 Jul 2024 15:53:54 +0900 Subject: [PATCH 09/25] WIP: proposal simulation for `erc20` module --- x/erc20/simulation/operation_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/erc20/simulation/operation_test.go b/x/erc20/simulation/operation_test.go index 978aafd2..df5ab38a 100644 --- a/x/erc20/simulation/operation_test.go +++ b/x/erc20/simulation/operation_test.go @@ -30,9 +30,11 @@ func TestWeightedOperations(t *testing.T) { weightedOps := simulation.WeightedOperations( appParams, cdc, + canto.Erc20Keeper, canto.AccountKeeper, canto.BankKeeper, - canto.Erc20Keeper, + canto.EvmKeeper, + canto.FeeMarketKeeper, ) s := rand.NewSource(2) From 2d65e23f9f422eedd177131a0f4639bbee221ffb Mon Sep 17 00:00:00 2001 From: poorphd Date: Mon, 8 Jul 2024 16:38:36 +0900 Subject: [PATCH 10/25] WIP: proposal simulation for `erc20` module --- x/erc20/module.go | 4 +- x/erc20/simulation/operation.go | 24 +++- x/erc20/simulation/operation_test.go | 2 +- x/erc20/simulation/proposal.go | 205 +++++++++++++++------------ 4 files changed, 132 insertions(+), 103 deletions(-) diff --git a/x/erc20/module.go b/x/erc20/module.go index 01d304a3..8753b4fc 100644 --- a/x/erc20/module.go +++ b/x/erc20/module.go @@ -171,5 +171,7 @@ func (am AppModule) RegisterStoreDecoder(decoderRegistry simtypes.StoreDecoderRe } func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return []simtypes.WeightedOperation{} + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.keeper, am.ak, am.bk, am.ek, am.fk, + ) } diff --git a/x/erc20/simulation/operation.go b/x/erc20/simulation/operation.go index 6ce9f5dc..cd3a83b6 100644 --- a/x/erc20/simulation/operation.go +++ b/x/erc20/simulation/operation.go @@ -30,9 +30,11 @@ const ( func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, + k keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper, - k keeper.Keeper, + ek types.EVMKeeper, + fk types.FeeMarketKeeper, ) simulation.WeightedOperations { var weightMsgConvertCoinNativeCoin int appParams.GetOrGenerate(OpWeightMsgConvertCoin, &weightMsgConvertCoinNativeCoin, nil, func(_ *rand.Rand) { @@ -47,17 +49,17 @@ func WeightedOperations( return simulation.WeightedOperations{ simulation.NewWeightedOperation( weightMsgConvertCoinNativeCoin, - SimulateMsgConvertCoin(ak, bk, k), + SimulateMsgConvertCoin(k, ak, bk), ), simulation.NewWeightedOperation( weightMsgConvertErc20NativeCoin, - SimulateMsgConvertErc20(ak, bk, k), + SimulateMsgConvertErc20(k, ak, bk, ek, fk), ), } } // SimulateMsgConvertCoin generates a MsgConvertCoin with random values for convertCoinNativeCoin -func SimulateMsgConvertCoin(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { +func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { @@ -65,7 +67,11 @@ func SimulateMsgConvertCoin(ak types.AccountKeeper, bk types.BankKeeper, k keepe pairs := k.GetTokenPairs(ctx) if len(pairs) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no pairs available"), nil, nil + _, err := SimulateRegisterCoin(r, ctx, accs, k, bk) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no pairs available"), nil, nil + } + pairs = k.GetTokenPairs(ctx) } // randomly pick one pair @@ -117,7 +123,7 @@ func SimulateMsgConvertCoin(ak types.AccountKeeper, bk types.BankKeeper, k keepe } // SimulateMsgConvertErc20 generates a MsgConvertErc20 with random values for convertERC20NativeCoin. -func SimulateMsgConvertErc20(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { +func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper, ek types.EVMKeeper, fk types.FeeMarketKeeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { @@ -125,7 +131,11 @@ func SimulateMsgConvertErc20(ak types.AccountKeeper, bk types.BankKeeper, k keep pairs := k.GetTokenPairs(ctx) if len(pairs) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil + _, err := SimulateRegisterERC20(r, ctx, accs, k, ak, bk, ek, fk) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil + } + pairs = k.GetTokenPairs(ctx) } // randomly pick one pair diff --git a/x/erc20/simulation/operation_test.go b/x/erc20/simulation/operation_test.go index df5ab38a..f86a0ffe 100644 --- a/x/erc20/simulation/operation_test.go +++ b/x/erc20/simulation/operation_test.go @@ -46,7 +46,7 @@ func TestWeightedOperations(t *testing.T) { opMsgRoute string opMsgName string }{ - {params.DefaultWeightMsgConvertCoin, types.ModuleName, types.TypeMsgConvertCoin}, + {params.DefaultWeightMsgConvertCoin, types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertCoin{})}, {params.DefaultWeightMsgConvertErc20, types.ModuleName, types.TypeMsgConvertERC20}, } diff --git a/x/erc20/simulation/proposal.go b/x/erc20/simulation/proposal.go index 8a777304..a4a4582f 100644 --- a/x/erc20/simulation/proposal.go +++ b/x/erc20/simulation/proposal.go @@ -74,126 +74,143 @@ func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) } } -func SimulateMsgRegisterCoin(k keeper.Keeper, bk types.BankKeeper) simtypes.MsgSimulatorFn { - return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { - coinMetadata := types.GenRandomCoinMetadata(r) - if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1)))); err != nil { - panic(err) - } - bankparams := bk.GetParams(ctx) - bankparams.DefaultSendEnabled = true - bk.SetParams(ctx, bankparams) +func SimulateRegisterCoin(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, bk types.BankKeeper) (sdk.Msg, error) { + coinMetadata := types.GenRandomCoinMetadata(r) + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1)))); err != nil { + panic(err) + } + bankparams := bk.GetParams(ctx) + bankparams.DefaultSendEnabled = true + bk.SetParams(ctx, bankparams) - params := k.GetParams(ctx) - params.EnableErc20 = true - k.SetParams(ctx, params) + params := k.GetParams(ctx) + params.EnableErc20 = true + k.SetParams(ctx, params) + + // mint cosmos coin to random accounts + randomIteration := r.Intn(10) + for i := 0; i < randomIteration; i++ { + simAccount, _ := simtypes.RandomAcc(r, accs) - // mint cosmos coin to random accounts - randomIteration := r.Intn(10) - for i := 0; i < randomIteration; i++ { - simAccount, _ := simtypes.RandomAcc(r, accs) - - if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { - panic(err) - } - if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { - panic(err) - } + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + return &types.MsgRegisterCoin{}, err } + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + return &types.MsgRegisterCoin{}, err + } + } - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") - msg := &types.MsgRegisterCoin{ - Authority: authority.String(), - Title: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 100), - Metadata: coinMetadata, - } + msg := &types.MsgRegisterCoin{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Metadata: coinMetadata, + } + + if _, err := k.RegisterCoinProposal(ctx, msg); err != nil { + return &types.MsgRegisterCoin{}, err + } + + return msg, nil +} - if _, err := k.RegisterCoinProposal(ctx, msg); err != nil { +func SimulateMsgRegisterCoin(k keeper.Keeper, bk types.BankKeeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { + msg, err := SimulateRegisterCoin(r, ctx, accs, k, bk) + if err != nil { panic(err) } - return msg } } -func SimulateMsgRegisterERC20(k keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, evmKeeper types.EVMKeeper, feemarketKeeper types.FeeMarketKeeper) simtypes.MsgSimulatorFn { - return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { // use the default gov module account address as authority - params := k.GetParams(ctx) - params.EnableErc20 = true - k.SetParams(ctx, params) +func SimulateRegisterERC20(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper, ek types.EVMKeeper, fk types.FeeMarketKeeper) (sdk.Msg, error) { + params := k.GetParams(ctx) + params.EnableErc20 = true + k.SetParams(ctx, params) - evmParams := evmtypes.DefaultParams() - evmParams.EvmDenom = "stake" - evmKeeper.SetParams(ctx, evmParams) + evmParams := evmtypes.DefaultParams() + evmParams.EvmDenom = "stake" + ek.SetParams(ctx, evmParams) - isNativeErc20 := r.Intn(2) == 1 - // account key - priv, err := ethsecp256k1.GenerateKey() - if err != nil { - panic(err) - } - addr := common.BytesToAddress(priv.PubKey().Address().Bytes()) - signer := tests.NewSigner(priv) + isNativeErc20 := r.Intn(2) == 1 - erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI + // account key + priv, err := ethsecp256k1.GenerateKey() + if err != nil { + panic(err) + } + addr := common.BytesToAddress(priv.PubKey().Address().Bytes()) + signer := tests.NewSigner(priv) - var deployer common.Address - var contractAddr common.Address - coinMetadata := types.GenRandomCoinMetadata(r) + erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI - coins := sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, sdkmath.NewInt(10000000000000000))) - if err = bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { - panic(err) - } + var deployer common.Address + var contractAddr common.Address + coinMetadata := types.GenRandomCoinMetadata(r) - if err = bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, coins); err != nil { - panic(err) + coins := sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, sdkmath.NewInt(10000000000000000))) + if err = bk.MintCoins(ctx, types.ModuleName, coins); err != nil { + return &types.MsgRegisterERC20{}, err + } + + if err = bk.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, coins); err != nil { + return &types.MsgRegisterERC20{}, err + } + if isNativeErc20 { + deployer = types.ModuleAddress + contractAddr, err = keeper.DeployERC20Contract(ctx, k, ak, coinMetadata) + } else { + deployer = addr + erc20Name := coinMetadata.Name + erc20Symbol := coinMetadata.Symbol + contractAddr, err = keeper.DeployContract(ctx, ek, fk, deployer, signer, erc20Name, erc20Symbol, erc20Decimals) + } + + // mint cosmos coin to random accounts + randomIteration := r.Intn(10) + for i := 0; i < randomIteration; i++ { + simAccount, _ := simtypes.RandomAcc(r, accs) + + mintAmt := sdkmath.NewInt(100000000) + receiver := common.BytesToAddress(simAccount.Address.Bytes()) + before := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + _, err = k.CallEVM(ctx, erc20ABI, deployer, contractAddr, true, "mint", receiver, mintAmt.BigInt()) + if err != nil { + return &types.MsgRegisterERC20{}, err } - if isNativeErc20 { - deployer = types.ModuleAddress - contractAddr, err = keeper.DeployERC20Contract(ctx, k, accountKeeper, coinMetadata) - } else { - deployer = addr - erc20Name := coinMetadata.Name - erc20Symbol := coinMetadata.Symbol - contractAddr, err = keeper.DeployContract(ctx, evmKeeper, feemarketKeeper, deployer, signer, erc20Name, erc20Symbol, erc20Decimals) + after := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + if after.Cmp(before.Add(before, mintAmt.BigInt())) != 0 { + return &types.MsgRegisterERC20{}, err } + } - // mint cosmos coin to random accounts - randomIteration := r.Intn(10) - for i := 0; i < randomIteration; i++ { - simAccount, _ := simtypes.RandomAcc(r, accs) - - mintAmt := sdkmath.NewInt(100000000) - receiver := common.BytesToAddress(simAccount.Address.Bytes()) - before := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) - _, err = k.CallEVM(ctx, erc20ABI, deployer, contractAddr, true, "mint", receiver, mintAmt.BigInt()) - if err != nil { - panic(err) - } - after := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) - if after.Cmp(before.Add(before, mintAmt.BigInt())) != 0 { - panic("mint failed") - } - } + // use the default gov module account address as authority + var authority sdk.AccAddress = address.Module("gov") - // use the default gov module account address as authority - var authority sdk.AccAddress = address.Module("gov") + msg := &types.MsgRegisterERC20{ + Authority: authority.String(), + Title: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Erc20Address: contractAddr.String(), + } - msg := &types.MsgRegisterERC20{ - Authority: authority.String(), - Title: simtypes.RandStringOfLength(r, 10), - Description: simtypes.RandStringOfLength(r, 100), - Erc20Address: contractAddr.String(), - } + if _, err := k.RegisterERC20Proposal(ctx, msg); err != nil { + return &types.MsgRegisterERC20{}, err + } + + return msg, nil +} - if _, err := k.RegisterERC20Proposal(ctx, msg); err != nil { +func SimulateMsgRegisterERC20(k keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, evmKeeper types.EVMKeeper, feemarketKeeper types.FeeMarketKeeper) simtypes.MsgSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) sdk.Msg { // use the default gov module account address as authority + msg, err := SimulateRegisterERC20(r, ctx, accs, k, accountKeeper, bankKeeper, evmKeeper, feemarketKeeper) + if err != nil { panic(err) } - return msg } } From f1af21cd660766f9409d471845fff0c1a66777d9 Mon Sep 17 00:00:00 2001 From: poorphd Date: Mon, 8 Jul 2024 23:41:41 +0900 Subject: [PATCH 11/25] feat: proposal simulation for `erc20` module --- x/erc20/simulation/operation.go | 31 ++++++++++++++++++++++++---- x/erc20/simulation/operation_test.go | 13 ++++++++++-- x/erc20/simulation/proposal.go | 30 +++++++-------------------- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/x/erc20/simulation/operation.go b/x/erc20/simulation/operation.go index cd3a83b6..685646dd 100644 --- a/x/erc20/simulation/operation.go +++ b/x/erc20/simulation/operation.go @@ -118,7 +118,8 @@ func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.Ba Bankkeeper: bk, ModuleName: types.ModuleName, } - return simulation.GenAndDeliverTxWithRandFees(txCtx) + op, fOps, err := simulation.GenAndDeliverTxWithRandFees(txCtx) + return op, fOps, err } } @@ -127,10 +128,11 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - pairs := k.GetTokenPairs(ctx) if len(pairs) == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil + _, err := SimulateRegisterERC20(r, ctx, accs, k, ak, bk, ek, fk) if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil @@ -141,10 +143,29 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B // randomly pick one pair pair := pairs[r.Intn(len(pairs))] + erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI + deployer := types.ModuleAddress + contractAddr := pair.GetERC20Contract() + randomIteration := r.Intn(10) + for i := 0; i < randomIteration; i++ { + simAccount, _ := simtypes.RandomAcc(r, accs) + + mintAmt := sdkmath.NewInt(1000000000) + receiver := common.BytesToAddress(simAccount.Address.Bytes()) + before := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + _, err := k.CallEVM(ctx, erc20ABI, deployer, contractAddr, true, "mint", receiver, mintAmt.BigInt()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + } + after := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) + if after.Cmp(before.Add(before, mintAmt.BigInt())) != 0 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + } + } + // select random account that has coins baseDenom var simAccount simtypes.Account var erc20Balance *big.Int - erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI skip := true for i := 0; i < len(accs); i++ { @@ -175,6 +196,8 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B Bankkeeper: bk, ModuleName: types.ModuleName, } - return simulation.GenAndDeliverTxWithRandFees(txCtx) + + op, fOps, err := simulation.GenAndDeliverTxWithRandFees(txCtx) + return op, fOps, err } } diff --git a/x/erc20/simulation/operation_test.go b/x/erc20/simulation/operation_test.go index f86a0ffe..e8d183f1 100644 --- a/x/erc20/simulation/operation_test.go +++ b/x/erc20/simulation/operation_test.go @@ -7,6 +7,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/Canto-Network/Canto/v7/app/params" + abci "github.com/cometbft/cometbft/abci/types" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" @@ -47,11 +48,12 @@ func TestWeightedOperations(t *testing.T) { opMsgName string }{ {params.DefaultWeightMsgConvertCoin, types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertCoin{})}, - {params.DefaultWeightMsgConvertErc20, types.ModuleName, types.TypeMsgConvertERC20}, + {params.DefaultWeightMsgConvertErc20, types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{})}, } for i, w := range weightedOps { - opMsg, _, _ := w.Op()(r, canto.BaseApp, ctx, accs, ctx.ChainID()) + opMsg, _, err := w.Op()(r, canto.BaseApp, ctx, accs, ctx.ChainID()) + require.NoError(t, err) require.Equal(t, expected[i].weight, w.Weight()) require.Equal(t, expected[i].opMsgRoute, opMsg.Route) require.Equal(t, expected[i].opMsgName, opMsg.Name) @@ -74,6 +76,13 @@ func createTestApp(t *testing.T, isCheckTx bool) (*app.Canto, sdk.Context) { ProposerAddress: consAddr, }) ctx = ctx.WithChainID("canto_9001-1") + app.FinalizeBlock( + &abci.RequestFinalizeBlock{ + Height: 1, + ProposerAddress: consAddr, + }, + ) + return app, ctx } diff --git a/x/erc20/simulation/proposal.go b/x/erc20/simulation/proposal.go index a4a4582f..ff8b9786 100644 --- a/x/erc20/simulation/proposal.go +++ b/x/erc20/simulation/proposal.go @@ -76,7 +76,7 @@ func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) func SimulateRegisterCoin(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, bk types.BankKeeper) (sdk.Msg, error) { coinMetadata := types.GenRandomCoinMetadata(r) - if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1)))); err != nil { + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1000000000000)))); err != nil { panic(err) } bankparams := bk.GetParams(ctx) @@ -92,12 +92,13 @@ func SimulateRegisterCoin(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account for i := 0; i < randomIteration; i++ { simAccount, _ := simtypes.RandomAcc(r, accs) - if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1000000000)))); err != nil { return &types.MsgRegisterCoin{}, err } - if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(100000000)))); err != nil { + if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1000000000)))); err != nil { return &types.MsgRegisterCoin{}, err } + } // use the default gov module account address as authority @@ -136,8 +137,6 @@ func SimulateRegisterERC20(r *rand.Rand, ctx sdk.Context, accs []simtypes.Accoun evmParams.EvmDenom = "stake" ek.SetParams(ctx, evmParams) - isNativeErc20 := r.Intn(2) == 1 - // account key priv, err := ethsecp256k1.GenerateKey() if err != nil { @@ -152,23 +151,10 @@ func SimulateRegisterERC20(r *rand.Rand, ctx sdk.Context, accs []simtypes.Accoun var contractAddr common.Address coinMetadata := types.GenRandomCoinMetadata(r) - coins := sdk.NewCoins(sdk.NewCoin(evmParams.EvmDenom, sdkmath.NewInt(10000000000000000))) - if err = bk.MintCoins(ctx, types.ModuleName, coins); err != nil { - return &types.MsgRegisterERC20{}, err - } - - if err = bk.SendCoinsFromModuleToModule(ctx, types.ModuleName, authtypes.FeeCollectorName, coins); err != nil { - return &types.MsgRegisterERC20{}, err - } - if isNativeErc20 { - deployer = types.ModuleAddress - contractAddr, err = keeper.DeployERC20Contract(ctx, k, ak, coinMetadata) - } else { - deployer = addr - erc20Name := coinMetadata.Name - erc20Symbol := coinMetadata.Symbol - contractAddr, err = keeper.DeployContract(ctx, ek, fk, deployer, signer, erc20Name, erc20Symbol, erc20Decimals) - } + deployer = addr + erc20Name := coinMetadata.Name + erc20Symbol := coinMetadata.Symbol + contractAddr, err = keeper.DeployContract(ctx, ek, fk, deployer, signer, erc20Name, erc20Symbol, erc20Decimals) // mint cosmos coin to random accounts randomIteration := r.Intn(10) From cce1043074a9719ccd3c2399208dfacdcba1ddd3 Mon Sep 17 00:00:00 2001 From: poorphd Date: Tue, 9 Jul 2024 12:53:18 +0900 Subject: [PATCH 12/25] chore: missing methods for mock keepers --- x/erc20/keeper/keeper_test.go | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/x/erc20/keeper/keeper_test.go b/x/erc20/keeper/keeper_test.go index 0744aa54..8c29a113 100644 --- a/x/erc20/keeper/keeper_test.go +++ b/x/erc20/keeper/keeper_test.go @@ -482,6 +482,26 @@ type MockEVMKeeper struct { mock.Mock } +func (m *MockEVMKeeper) SetParams(ctx sdk.Context, params evmtypes.Params) error { + //TODO implement me + panic("implement me") +} + +func (m *MockEVMKeeper) ChainID() *big.Int { + //TODO implement me + panic("implement me") +} + +func (m *MockEVMKeeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 { + //TODO implement me + panic("implement me") +} + +func (m *MockEVMKeeper) EthereumTx(goCtx context.Context, msg *evmtypes.MsgEthereumTx) (*evmtypes.MsgEthereumTxResponse, error) { + //TODO implement me + panic("implement me") +} + func (m *MockEVMKeeper) GetParams(ctx sdk.Context) evmtypes.Params { args := m.Called(mock.Anything) return args.Get(0).(evmtypes.Params) @@ -518,6 +538,26 @@ type MockBankKeeper struct { mock.Mock } +func (b *MockBankKeeper) SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error { + //TODO implement me + panic("implement me") +} + +func (b *MockBankKeeper) SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins { + //TODO implement me + panic("implement me") +} + +func (b *MockBankKeeper) GetParams(ctx context.Context) banktypes.Params { + //TODO implement me + panic("implement me") +} + +func (b *MockBankKeeper) SetParams(ctx context.Context, params banktypes.Params) error { + //TODO implement me + panic("implement me") +} + func (b *MockBankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error { args := b.Called(mock.Anything, mock.Anything, mock.Anything, mock.Anything) return args.Error(0) From 447a4d7391e3315b49f0718e3b24b02530711f49 Mon Sep 17 00:00:00 2001 From: poorphd Date: Tue, 9 Jul 2024 19:57:52 +0900 Subject: [PATCH 13/25] refactor: Remove RandomizedParams from AppModuleSimulation interface --- x/epochs/module.go | 6 ------ x/erc20/module.go | 5 ----- x/govshuttle/module_simulation.go | 8 -------- x/inflation/module.go | 6 ------ x/onboarding/module.go | 5 ----- 5 files changed, 30 deletions(-) diff --git a/x/epochs/module.go b/x/epochs/module.go index 06cad8fe..0b856169 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -165,11 +164,6 @@ func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.We return []simtypes.WeightedProposalContent{} } -// RandomizedParams creates randomizedepochs param changes for the simulator. -func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{} -} - // RegisterStoreDecoder registers a decoder for supply module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { } diff --git a/x/erc20/module.go b/x/erc20/module.go index 8753b4fc..d159103f 100644 --- a/x/erc20/module.go +++ b/x/erc20/module.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "github.com/Canto-Network/Canto/v7/x/erc20/simulation" abci "github.com/cometbft/cometbft/abci/types" @@ -163,10 +162,6 @@ func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes return []simtypes.WeightedProposalContent{} } -func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{} -} - func (am AppModule) RegisterStoreDecoder(decoderRegistry simtypes.StoreDecoderRegistry) { } diff --git a/x/govshuttle/module_simulation.go b/x/govshuttle/module_simulation.go index 4ceed0de..cfe1bb57 100644 --- a/x/govshuttle/module_simulation.go +++ b/x/govshuttle/module_simulation.go @@ -1,8 +1,6 @@ package govshuttle import ( - "math/rand" - //"github.com/Canto-Network/Canto/v2/testutil/sample" govshuttlesimulation "github.com/Canto-Network/Canto/v7/x/govshuttle/simulation" "github.com/Canto-Network/Canto/v7/x/govshuttle/types" @@ -44,12 +42,6 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP return nil } -// RandomizedParams creates randomized param changes for the simulator -func (am AppModule) RandomizedParams(_ *rand.Rand) []simtypes.LegacyParamChange { - - return []simtypes.LegacyParamChange{} -} - // RegisterStoreDecoder registers a decoder func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} diff --git a/x/inflation/module.go b/x/inflation/module.go index ffe593e6..467fcee7 100644 --- a/x/inflation/module.go +++ b/x/inflation/module.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "github.com/Canto-Network/Canto/v7/x/inflation/simulation" abci "github.com/cometbft/cometbft/abci/types" @@ -178,11 +177,6 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight return simulation.ProposalMsgs() } -// RandomizedParams creates randomized inflation param changes for the simulator. -func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{} -} - // RegisterStoreDecoder registers a decoder for inflation module's types. func (am AppModule) RegisterStoreDecoder(decoderRegistry simtypes.StoreDecoderRegistry) { } diff --git a/x/onboarding/module.go b/x/onboarding/module.go index 7cbac388..a4b75dca 100644 --- a/x/onboarding/module.go +++ b/x/onboarding/module.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -140,10 +139,6 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP return []simtypes.WeightedProposalContent{} } -func (AppModule) RandomizedParams(_ *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{} -} - func (AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) { } From fe7b5b8de4c1113d7f0281b000670bf60e394006 Mon Sep 17 00:00:00 2001 From: poorphd Date: Tue, 9 Jul 2024 22:57:49 +0900 Subject: [PATCH 14/25] fix: NoOpMsg for disabled pair --- x/erc20/simulation/operation.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x/erc20/simulation/operation.go b/x/erc20/simulation/operation.go index 685646dd..65de0221 100644 --- a/x/erc20/simulation/operation.go +++ b/x/erc20/simulation/operation.go @@ -76,6 +76,9 @@ func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.Ba // randomly pick one pair pair := pairs[r.Intn(len(pairs))] + if !pair.Enabled { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "token pair is not enabled"), nil, nil + } baseDenom := pair.GetDenom() // select random account that has coins baseDenom @@ -142,6 +145,9 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B // randomly pick one pair pair := pairs[r.Intn(len(pairs))] + if !pair.Enabled { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "token pair is not enabled"), nil, nil + } erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI deployer := types.ModuleAddress From df52384b48ac162fea53ae3caffb7a0938b30735 Mon Sep 17 00:00:00 2001 From: poorphd Date: Tue, 9 Jul 2024 23:35:13 +0900 Subject: [PATCH 15/25] fix: NoOpMsg types --- .../{operations.go => operation.go} | 92 ++++++++++--------- x/coinswap/simulation/operation_test.go | 6 +- x/coinswap/types/msgs.go | 25 ----- x/erc20/simulation/operation.go | 18 ++-- x/erc20/types/msg.go | 5 - 5 files changed, 60 insertions(+), 86 deletions(-) rename x/coinswap/simulation/{operations.go => operation.go} (76%) diff --git a/x/coinswap/simulation/operations.go b/x/coinswap/simulation/operation.go similarity index 76% rename from x/coinswap/simulation/operations.go rename to x/coinswap/simulation/operation.go index d1b25848..17fec55b 100644 --- a/x/coinswap/simulation/operations.go +++ b/x/coinswap/simulation/operation.go @@ -28,6 +28,12 @@ const ( OpWeightMsgRemoveLiquidity = "op_weight_msg_remove_liquidity" ) +var ( + TypeMsgSwapOrder = sdk.MsgTypeURL(&types.MsgSwapOrder{}) + TypeMsgAddLiquidity = sdk.MsgTypeURL(&types.MsgAddLiquidity{}) + TypeMsgRemoveLiquidity = sdk.MsgTypeURL(&types.MsgRemoveLiquidity{}) +) + func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, @@ -99,31 +105,31 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B exactStandardAmt := simtypes.RandomAmount(r, spendable.AmountOf(standardDenom)) if !exactStandardAmt.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "standardAmount should be positive"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "standardAmount should be positive"), nil, nil } maxToken = RandomSpendableToken(r, spendable) if maxToken.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "tokenDenom should not be standardDenom"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be standardDenom"), nil, err } _, err = k.GetMaximumSwapAmount(ctx, maxToken.Denom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, err.Error()), nil, nil } if strings.HasPrefix(maxToken.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "tokenDenom should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be liquidity token"), nil, err } if !maxToken.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "maxToken must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "maxToken must is positive"), nil, err } poolId := types.GetPoolId(maxToken.Denom) pool, has := k.GetPool(ctx, poolId) if has { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "pool not found"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "pool not found"), nil, err } reservePool, err := k.GetPoolBalances(ctx, pool.EscrowAddress) @@ -136,7 +142,7 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B minLiquidity = liquidity.Mul(exactStandardAmt).Quo(standardReserveAmt) if !maxToken.Amount.Sub(reservePool.AmountOf(maxToken.GetDenom()).Mul(exactStandardAmt).Quo(standardReserveAmt)).IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "insufficient funds"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err } params := k.GetParams(ctx) @@ -147,7 +153,7 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B spendTotal = spendTotal.Add(exactStandardAmt) } if spendable.AmountOf(poolCreationFee.Denom).LT(spendTotal) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddLiquidity, "insufficient funds"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err } } @@ -165,7 +171,7 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to generate fees"), nil, nil } } @@ -184,11 +190,11 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to generate mock tx"), nil, err } if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(msg, true, ""), nil, nil @@ -214,7 +220,7 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank standardDenom, _ := k.GetStandardDenom(ctx) if spendable.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "spendable is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "spendable is zero"), nil, err } // sold coin @@ -222,25 +228,25 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } if strings.HasPrefix(inputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should not be liquidity token"), nil, err } if !inputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin must is positive"), nil, err } poolId := types.GetPoolId(inputCoin.Denom) pool, has := k.GetPool(ctx, poolId) if !has { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } // bought coin @@ -250,34 +256,34 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank return false }) if coins.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "total supply is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "total supply is zero"), nil, err } outputCoin = RandomTotalToken(r, coins) _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } if strings.HasPrefix(outputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin should not be liquidity token"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin should not be liquidity token"), nil, err } if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err } poolId = types.GetPoolId(outputCoin.Denom) pool, has = k.GetPool(ctx, poolId) if !has { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil } if outputCoin.Denom == inputCoin.Denom { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin denom and inputcoin denom should not be the same"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin denom and inputcoin denom should not be the same"), nil, nil } isDoubleSwap := (outputCoin.Denom != standardDenom) && (inputCoin.Denom != standardDenom) @@ -286,26 +292,26 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank if isBuyOrder && isDoubleSwap { inputCoin, outputCoin, err = doubleSwapBill(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } } else if isBuyOrder && !isDoubleSwap { inputCoin, outputCoin, err = singleSwapBill(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } } else if !isBuyOrder && isDoubleSwap { inputCoin, outputCoin, err = doubleSwapSellOrder(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } } else if !isBuyOrder && !isDoubleSwap { inputCoin, outputCoin, err = singleSwapSellOrder(inputCoin, outputCoin, ctx, k) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, err.Error()), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } } if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "outputCoin must is positive"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err } deadline := randDeadline(r) @@ -327,7 +333,7 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to generate fees"), nil, nil } } @@ -345,11 +351,11 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to generate mock tx"), nil, err } if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(msg, true, ""), nil, nil @@ -375,23 +381,23 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type spendable := bk.SpendableCoins(ctx, account.GetAddress()) if spendable.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "spendable is zero"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "spendable is zero"), nil, err } token := RandomSpendableToken(r, spendable) if token.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "tokenDenom should not be standardDenom"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "tokenDenom should not be standardDenom"), nil, err } pool, has := k.GetPoolByLptDenom(ctx, token.Denom) if !has { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "inputCoin should exist in the pool"), nil, nil } reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "inputCoin should exist in the pool"), nil, nil } standardReserveAmt := reservePool.AmountOf(standardDenom) @@ -401,20 +407,20 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type liquidityReserve := bk.GetSupply(ctx, token.Denom).Amount if !withdrawLiquidity.IsValid() || !withdrawLiquidity.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "invalid withdrawLiquidity"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "invalid withdrawLiquidity"), nil, nil } if liquidityReserve.LT(withdrawLiquidity.Amount) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } minToken = withdrawLiquidity.Amount.Mul(tokenReserveAmt).Quo(liquidityReserve) if tokenReserveAmt.LT(minToken) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } minStandardAmt = withdrawLiquidity.Amount.Mul(standardReserveAmt).Quo(liquidityReserve) if standardReserveAmt.LT(minStandardAmt) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "insufficient funds"), nil, nil } deadline := randDeadline(r) @@ -431,7 +437,7 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate fees"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "unable to generate fees"), nil, nil } } @@ -450,11 +456,11 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type ) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "unable to generate mock tx"), nil, err } if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "unable to deliver tx"), nil, nil } return simtypes.NewOperationMsg(msg, true, ""), nil, nil diff --git a/x/coinswap/simulation/operation_test.go b/x/coinswap/simulation/operation_test.go index 5f7e58b4..3df06f99 100644 --- a/x/coinswap/simulation/operation_test.go +++ b/x/coinswap/simulation/operation_test.go @@ -44,9 +44,9 @@ func TestWeightedOperations(t *testing.T) { opMsgRoute string opMsgName string }{ - {params.DefaultWeightMsgAddLiquidity, types.ModuleName, types.TypeMsgAddLiquidity}, - {params.DefaultWeightMsgSwapOrder, types.ModuleName, types.TypeMsgSwapOrder}, - {params.DefaultWeightMsgRemoveLiquidity, types.ModuleName, types.TypeMsgRemoveLiquidity}, + {params.DefaultWeightMsgAddLiquidity, types.ModuleName, sdk.MsgTypeURL(&types.MsgAddLiquidity{})}, + {params.DefaultWeightMsgSwapOrder, types.ModuleName, sdk.MsgTypeURL(&types.MsgSwapOrder{})}, + {params.DefaultWeightMsgRemoveLiquidity, types.ModuleName, sdk.MsgTypeURL(&types.MsgRemoveLiquidity{})}, } for i, w := range weightedOps { diff --git a/x/coinswap/types/msgs.go b/x/coinswap/types/msgs.go index 13c5a0ce..0df84f59 100644 --- a/x/coinswap/types/msgs.go +++ b/x/coinswap/types/msgs.go @@ -18,13 +18,6 @@ const ( LptTokenPrefix = "lpt" // LptTokenFormat defines the name of liquidity token LptTokenFormat = "lpt-%d" - - // TypeMsgAddLiquidity defines the type of MsgAddLiquidity - TypeMsgAddLiquidity = "add_liquidity" - // TypeMsgRemoveLiquidity defines the type of MsgRemoveLiquidity - TypeMsgRemoveLiquidity = "remove_liquidity" - // TypeMsgSwapOrder defines the type of MsgSwapOrder - TypeMsgSwapOrder = "swap_order" ) /* --------------------------------------------------------------------------- */ @@ -52,12 +45,6 @@ func NewMsgSwapOrder( } } -// Route implements Msg. -func (msg MsgSwapOrder) Route() string { return RouterKey } - -// Type implements Msg. -func (msg MsgSwapOrder) Type() string { return TypeMsgSwapOrder } - // ValidateBasic implements Msg. func (msg MsgSwapOrder) ValidateBasic() error { if err := ValidateInput(msg.Input); err != nil { @@ -110,12 +97,6 @@ func NewMsgAddLiquidity( } } -// Route implements Msg. -func (msg MsgAddLiquidity) Route() string { return RouterKey } - -// Type implements Msg. -func (msg MsgAddLiquidity) Type() string { return TypeMsgAddLiquidity } - // ValidateBasic implements Msg. func (msg MsgAddLiquidity) ValidateBasic() error { if err := ValidateMaxToken(msg.MaxToken); err != nil { @@ -175,12 +156,6 @@ func NewMsgRemoveLiquidity( } } -// Route implements Msg. -func (msg MsgRemoveLiquidity) Route() string { return RouterKey } - -// Type implements Msg. -func (msg MsgRemoveLiquidity) Type() string { return TypeMsgRemoveLiquidity } - // ValidateBasic implements Msg. func (msg MsgRemoveLiquidity) ValidateBasic() error { if err := ValidateMinToken(msg.MinToken); err != nil { diff --git a/x/erc20/simulation/operation.go b/x/erc20/simulation/operation.go index 65de0221..9dda50ec 100644 --- a/x/erc20/simulation/operation.go +++ b/x/erc20/simulation/operation.go @@ -69,7 +69,7 @@ func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.Ba if len(pairs) == 0 { _, err := SimulateRegisterCoin(r, ctx, accs, k, bk) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no pairs available"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertCoin{}), "no pairs available"), nil, nil } pairs = k.GetTokenPairs(ctx) } @@ -77,7 +77,7 @@ func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.Ba // randomly pick one pair pair := pairs[r.Intn(len(pairs))] if !pair.Enabled { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "token pair is not enabled"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertCoin{}), "token pair is not enabled"), nil, nil } baseDenom := pair.GetDenom() @@ -96,7 +96,7 @@ func SimulateMsgConvertCoin(k keeper.Keeper, ak types.AccountKeeper, bk types.Ba } if skip { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "no account has coins"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertCoin{}), "no account has coins"), nil, nil } priv, _ := ethsecp256k1.GenerateKey() @@ -134,11 +134,9 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B pairs := k.GetTokenPairs(ctx) if len(pairs) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil - _, err := SimulateRegisterERC20(r, ctx, accs, k, ak, bk, ek, fk) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no pairs available"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{}), "no pairs available"), nil, nil } pairs = k.GetTokenPairs(ctx) } @@ -146,7 +144,7 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B // randomly pick one pair pair := pairs[r.Intn(len(pairs))] if !pair.Enabled { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertCoin, "token pair is not enabled"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{}), "token pair is not enabled"), nil, nil } erc20ABI := contracts.ERC20MinterBurnerDecimalsContract.ABI @@ -161,11 +159,11 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B before := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) _, err := k.CallEVM(ctx, erc20ABI, deployer, contractAddr, true, "mint", receiver, mintAmt.BigInt()) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{}), "no account has native ERC20"), nil, nil } after := k.BalanceOf(ctx, erc20ABI, contractAddr, receiver) if after.Cmp(before.Add(before, mintAmt.BigInt())) != 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{}), "no account has native ERC20"), nil, nil } } @@ -184,7 +182,7 @@ func SimulateMsgConvertErc20(k keeper.Keeper, ak types.AccountKeeper, bk types.B } if skip { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgConvertERC20, "no account has native ERC20"), nil, nil + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(&types.MsgConvertERC20{}), "no account has native ERC20"), nil, nil } msg := types.NewMsgConvertERC20(sdkmath.NewIntFromBigInt(erc20Balance), simAccount.Address, pair.GetERC20Contract(), common.BytesToAddress(simAccount.Address.Bytes())) diff --git a/x/erc20/types/msg.go b/x/erc20/types/msg.go index 45304fa8..69c201f5 100644 --- a/x/erc20/types/msg.go +++ b/x/erc20/types/msg.go @@ -18,11 +18,6 @@ var ( _ sdk.Msg = &MsgConvertERC20{} ) -const ( - TypeMsgConvertCoin = "convert_coin" - TypeMsgConvertERC20 = "convert_ERC20" -) - // NewMsgConvertCoin creates a new instance of MsgConvertCoin func NewMsgConvertCoin(coin sdk.Coin, receiver common.Address, sender sdk.AccAddress) *MsgConvertCoin { // nolint: interfacer return &MsgConvertCoin{ From 45ac147f9d95aa794e4851f68b520bb551a8b97c Mon Sep 17 00:00:00 2001 From: poorphd Date: Wed, 24 Jul 2024 13:50:37 +0900 Subject: [PATCH 16/25] fix: msg route registeration --- x/coinswap/simulation/operation.go | 3 +-- x/erc20/simulation/decoder.go | 2 +- x/govshuttle/module.go | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/x/coinswap/simulation/operation.go b/x/coinswap/simulation/operation.go index 61571a69..8fb2cb44 100644 --- a/x/coinswap/simulation/operation.go +++ b/x/coinswap/simulation/operation.go @@ -8,8 +8,7 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - "github.com/Canto-Network/Canto/v7/x/coinswap/keeper" - "github.com/Canto-Network/Canto/v7/x/coinswap/types" + "github.com/Canto-Network/Canto/v7/app/params" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" diff --git a/x/erc20/simulation/decoder.go b/x/erc20/simulation/decoder.go index ff9d530c..c650a1d5 100644 --- a/x/erc20/simulation/decoder.go +++ b/x/erc20/simulation/decoder.go @@ -21,7 +21,7 @@ func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { cdc.MustUnmarshal(kvB.Value, &tpB) return fmt.Sprintf("%v\n%v", tpA, tpB) - case bytes.Equal(kvA.Key[:1], types.KeyPrefixTokenPairByERC20): + case bytes.Equal(kvA.Key[:1], types.KeyPrefixTokenPairByERC20Address): var tpA, tpB types.TokenPair cdc.MustUnmarshal(kvA.Value, &tpA) cdc.MustUnmarshal(kvB.Value, &tpB) diff --git a/x/govshuttle/module.go b/x/govshuttle/module.go index ec29f14e..60bf666a 100644 --- a/x/govshuttle/module.go +++ b/x/govshuttle/module.go @@ -132,7 +132,6 @@ func (am AppModule) Name() string { func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) - types.RegisterMsgServer(cfg.MsgServer(), am.keeper) } // RegisterInvariants registers the capability module's invariants. From 66d4aead1a340aa2e77ea3862b7203b39066e9ac Mon Sep 17 00:00:00 2001 From: poorphd Date: Wed, 24 Jul 2024 17:11:12 +0900 Subject: [PATCH 17/25] fix: key prefix name --- x/erc20/simulation/decoder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/erc20/simulation/decoder_test.go b/x/erc20/simulation/decoder_test.go index e051955e..583f2394 100644 --- a/x/erc20/simulation/decoder_test.go +++ b/x/erc20/simulation/decoder_test.go @@ -24,7 +24,7 @@ func TestERC20Store(t *testing.T) { kvPairs := kv.Pairs{ Pairs: []kv.Pair{ {Key: types.KeyPrefixTokenPair, Value: cdc.MustMarshal(&tokenPair)}, - {Key: types.KeyPrefixTokenPairByERC20, Value: cdc.MustMarshal(&tokenPair)}, + {Key: types.KeyPrefixTokenPairByERC20Address, Value: cdc.MustMarshal(&tokenPair)}, {Key: types.KeyPrefixTokenPairByDenom, Value: cdc.MustMarshal(&tokenPair)}, {Key: []byte{0x99}, Value: []byte{0x99}}, }, From 1e4dd7e7811483072a32807a39fa2f6eeae52e53 Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 25 Jul 2024 17:04:17 +0900 Subject: [PATCH 18/25] fix: coinswap module's operation simulation --- app/params/weights.go | 4 +- x/coinswap/simulation/operation.go | 398 ++++++++++++++++------------- 2 files changed, 217 insertions(+), 185 deletions(-) diff --git a/app/params/weights.go b/app/params/weights.go index 661c8b76..8e1ce11a 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -10,7 +10,7 @@ const ( DefaultWeightMsgConvertCoin int = 20 DefaultWeightMsgConvertErc20 int = 20 - DefaultWeightMsgSwapOrder int = 10 - DefaultWeightMsgAddLiquidity int = 20 + DefaultWeightMsgSwapOrder int = 50 + DefaultWeightMsgAddLiquidity int = 10 DefaultWeightMsgRemoveLiquidity int = 10 ) diff --git a/x/coinswap/simulation/operation.go b/x/coinswap/simulation/operation.go index 8fb2cb44..eaf88be2 100644 --- a/x/coinswap/simulation/operation.go +++ b/x/coinswap/simulation/operation.go @@ -1,12 +1,11 @@ package simulation import ( - "fmt" + "errors" "math/rand" "strings" "time" - errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" "github.com/Canto-Network/Canto/v7/app/params" @@ -94,6 +93,10 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B opMsg simtypes.OperationMsg, fOps []simtypes.FutureOperation, err error, ) { simAccount, _ := simtypes.RandomAcc(r, accs) + err = FundAccount(r, ctx, k, bk, simAccount.Address) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to fund account"), nil, err + } account := ak.GetAccount(ctx, simAccount.Address) var ( @@ -103,57 +106,75 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B standardDenom, _ := k.GetStandardDenom(ctx) spendable := bk.SpendableCoins(ctx, account.GetAddress()) - exactStandardAmt := simtypes.RandomAmount(r, spendable.AmountOf(standardDenom)) - - if !exactStandardAmt.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "standardAmount should be positive"), nil, nil + exactStandardAmt, err := simtypes.RandPositiveInt(r, spendable.AmountOf(standardDenom)) + if err != nil { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "standardAmount should be positive", + ), nil, nil } - maxToken = RandomSpendableToken(r, spendable) - if maxToken.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be standardDenom"), nil, err + maxToken, err = randToken(r, spendable) + if err != nil { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "insufficient funds", + ), nil, nil } - _, err = k.GetMaximumSwapAmount(ctx, maxToken.Denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, err.Error()), nil, nil + if maxToken.Denom == standardDenom { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "tokenDenom should not be standardDenom", + ), nil, nil } if strings.HasPrefix(maxToken.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "tokenDenom should not be liquidity token"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "tokenDenom should not be liquidity token", + ), nil, nil } if !maxToken.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "maxToken must is positive"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "maxToken must is positive", + ), nil, err } - poolId := types.GetPoolId(maxToken.Denom) - pool, has := k.GetPool(ctx, poolId) - if has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "pool not found"), nil, err - } - - reservePool, err := k.GetPoolBalances(ctx, pool.EscrowAddress) - - if err != nil { - minLiquidity = exactStandardAmt - } else { - standardReserveAmt := reservePool.AmountOf(standardDenom) - liquidity := bk.GetSupply(ctx, pool.LptDenom).Amount - minLiquidity = liquidity.Mul(exactStandardAmt).Quo(standardReserveAmt) - - if !maxToken.Amount.Sub(reservePool.AmountOf(maxToken.GetDenom()).Mul(exactStandardAmt).Quo(standardReserveAmt)).IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err - } - - params := k.GetParams(ctx) - poolCreationFee := params.PoolCreationFee - + poolID := types.GetPoolId(maxToken.Denom) + pool, has := k.GetPool(ctx, poolID) + if !has { + poolCreationFee := k.GetParams(ctx).PoolCreationFee spendTotal := poolCreationFee.Amount if strings.EqualFold(poolCreationFee.Denom, standardDenom) { spendTotal = spendTotal.Add(exactStandardAmt) } if spendable.AmountOf(poolCreationFee.Denom).LT(spendTotal) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "insufficient funds", + ), nil, err + } + minLiquidity = exactStandardAmt + } else { + balances, err := k.GetPoolBalances(ctx, pool.EscrowAddress) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "pool address not found"), nil, err + } + + standardReserveAmt := balances.AmountOf(standardDenom) + liquidity := bk.GetSupply(ctx, pool.LptDenom).Amount + minLiquidity = liquidity.Mul(exactStandardAmt).Quo(standardReserveAmt) + + if !maxToken.Amount.Sub(balances.AmountOf(maxToken.Denom).Mul(exactStandardAmt).Quo(standardReserveAmt)).IsPositive() { return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "insufficient funds"), nil, err } } @@ -168,16 +189,20 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B ) var fees sdk.Coins - coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoins(sdk.NewCoin(standardDenom, exactStandardAmt), maxToken)...) + coinsTemp, hasNeg := spendable.SafeSub( + sdk.NewCoins(sdk.NewCoin(standardDenom, exactStandardAmt), maxToken)...) if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to generate fees"), nil, nil + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "unable to generate fees", + ), nil, nil } } txGen := moduletestutil.MakeTestEncodingConfig().TxConfig - tx, err := simtestutil.GenSignedMockTx( r, txGen, @@ -189,9 +214,12 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B []uint64{account.GetSequence()}, simAccount.PrivKey, ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "unable to generate mock tx", + ), nil, err } if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { @@ -215,104 +243,104 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank isBuyOrder bool ) + pools := k.GetAllPools(ctx) + if len(pools) == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "no pool found", + ), nil, nil + } + + pool := pools[r.Intn(len(pools))] + simAccount, _ := simtypes.RandomAcc(r, accs) + err = FundAccount(r, ctx, k, bk, simAccount.Address) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to fund account"), nil, err + } account := ak.GetAccount(ctx, simAccount.Address) spendable := bk.SpendableCoins(ctx, account.GetAddress()) standardDenom, _ := k.GetStandardDenom(ctx) if spendable.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "spendable is zero"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "spendable is zero", + ), nil, err } // sold coin - inputCoin = RandomSpendableToken(r, spendable) - - _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil - } - - if strings.HasPrefix(inputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should not be liquidity token"), nil, err - } - - if !inputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin must is positive"), nil, err - } - - poolId := types.GetPoolId(inputCoin.Denom) - pool, has := k.GetPool(ctx, poolId) - if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil - } - - if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil - } - - // bought coin - var coins sdk.Coins - bk.IterateTotalSupply(ctx, func(coin sdk.Coin) bool { - coins = append(coins, coin) - return false - }) - if coins.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "total supply is zero"), nil, err - } - outputCoin = RandomTotalToken(r, coins) - _, err = k.GetMaximumSwapAmount(ctx, inputCoin.Denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil - } - - if strings.HasPrefix(outputCoin.Denom, types.LptTokenPrefix) { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin should not be liquidity token"), nil, err - } - - if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err - } - - poolId = types.GetPoolId(outputCoin.Denom) - pool, has = k.GetPool(ctx, poolId) - if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil - } - - if _, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom); err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "inputCoin should exist in the pool"), nil, nil - } - - if outputCoin.Denom == inputCoin.Denom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin denom and inputcoin denom should not be the same"), nil, nil + tokenToStandard := randBoolean(r) + swapLimit := k.GetParams(ctx).MaxSwapAmount.AmountOf(pool.CounterpartyDenom) + if tokenToStandard { + inputCoin = sdk.NewCoin(pool.CounterpartyDenom, simtypes.RandomAmount(r, swapLimit)) + outputCoin = sdk.NewCoin(standardDenom, simtypes.RandomAmount(r, swapLimit)) + } else { + inputCoin = sdk.NewCoin(standardDenom, simtypes.RandomAmount(r, swapLimit)) + outputCoin = sdk.NewCoin(pool.CounterpartyDenom, simtypes.RandomAmount(r, swapLimit)) } - isDoubleSwap := (outputCoin.Denom != standardDenom) && (inputCoin.Denom != standardDenom) isBuyOrder = randBoolean(r) - if isBuyOrder && isDoubleSwap { - inputCoin, outputCoin, err = doubleSwapBill(inputCoin, outputCoin, ctx, k) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil - } - } else if isBuyOrder && !isDoubleSwap { + if isBuyOrder { inputCoin, outputCoin, err = singleSwapBill(inputCoin, outputCoin, ctx, k) if err != nil { return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } - } else if !isBuyOrder && isDoubleSwap { - inputCoin, outputCoin, err = doubleSwapSellOrder(inputCoin, outputCoin, ctx, k) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil + if tokenToStandard && inputCoin.Amount.GTE(swapLimit) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "inputCoin amount should be less than swapLimit", + ), nil, nil + } + if inputCoin.Amount.GTE(spendable.AmountOf(inputCoin.Denom)) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "insufficient funds", + ), nil, nil } - } else if !isBuyOrder && !isDoubleSwap { + } else { inputCoin, outputCoin, err = singleSwapSellOrder(inputCoin, outputCoin, ctx, k) if err != nil { return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, err.Error()), nil, nil } + if !tokenToStandard && outputCoin.Amount.GTE(swapLimit) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "outputCoin amount should be less than swapLimit", + ), nil, nil + } + if inputCoin.Amount.GTE(spendable.AmountOf(inputCoin.Denom)) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "insufficient funds", + ), nil, nil + } } + + reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) + standardReserveAmt := reservePool.AmountOf(standardDenom) + tokenReserveAmt := reservePool.AmountOf(pool.CounterpartyDenom) + if !standardReserveAmt.IsPositive() || !tokenReserveAmt.IsPositive() { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "reserve pool should be positive", + ), nil, nil + } + if !outputCoin.Amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "outputCoin must is positive"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "outputCoin must is positive", + ), nil, err } deadline := randDeadline(r) @@ -330,11 +358,16 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank ) var fees sdk.Coins - coinsTemp, hasNeg := spendable.SafeSub(sdk.NewCoins(sdk.NewCoin(inputCoin.Denom, inputCoin.Amount))...) + coinsTemp, hasNeg := spendable.SafeSub( + sdk.NewCoins(sdk.NewCoin(inputCoin.Denom, inputCoin.Amount))...) if !hasNeg { fees, err = simtypes.RandomFees(r, ctx, coinsTemp) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to generate fees"), nil, nil + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "unable to generate fees", + ), nil, nil } } @@ -350,9 +383,12 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank []uint64{account.GetSequence()}, simAccount.PrivKey, ) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to generate mock tx"), nil, err + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "unable to generate mock tx", + ), nil, err } if _, _, err := app.SimDeliver(txGen.TxEncoder(), tx); err != nil { @@ -370,7 +406,29 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type ) ( opMsg simtypes.OperationMsg, fOps []simtypes.FutureOperation, err error, ) { - simAccount, _ := simtypes.RandomAcc(r, accs) + + pools := k.GetAllPools(ctx) + if len(pools) == 0 { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "no pool found"), nil, nil + } + + pool := pools[r.Intn(len(pools))] + + simAccount, err := func(accs []simtypes.Account) (simtypes.Account, error) { + for _, acc := range accs { + coins := bk.GetAllBalances(ctx, acc.Address) + for _, coin := range coins { + if coin.Denom == pool.LptDenom { + return acc, nil + } + } + } + return simtypes.Account{}, errors.New("no account has LptCoin") + }(accs) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, err.Error()), nil, nil + } + account := ak.GetAccount(ctx, simAccount.Address) standardDenom, _ := k.GetStandardDenom(ctx) @@ -385,17 +443,6 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "spendable is zero"), nil, err } - token := RandomSpendableToken(r, spendable) - - if token.Denom == standardDenom { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "tokenDenom should not be standardDenom"), nil, err - } - - pool, has := k.GetPoolByLptDenom(ctx, token.Denom) - if !has { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "inputCoin should exist in the pool"), nil, nil - } - reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) if err != nil { return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "inputCoin should exist in the pool"), nil, nil @@ -404,8 +451,8 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type standardReserveAmt := reservePool.AmountOf(standardDenom) tokenReserveAmt := reservePool.AmountOf(pool.CounterpartyDenom) - withdrawLiquidity = sdk.NewCoin(token.GetDenom(), simtypes.RandomAmount(r, token.Amount)) - liquidityReserve := bk.GetSupply(ctx, token.Denom).Amount + withdrawLiquidity = sdk.NewCoin(pool.LptDenom, simtypes.RandomAmount(r, spendable.AmountOf(pool.LptDenom))) + liquidityReserve := bk.GetSupply(ctx, pool.LptDenom).Amount if !withdrawLiquidity.IsValid() || !withdrawLiquidity.IsPositive() { return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "invalid withdrawLiquidity"), nil, nil @@ -469,6 +516,37 @@ func SimulateMsgRemoveLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk type } } +func FundAccount(r *rand.Rand, ctx sdk.Context, k keeper.Keeper, bk types.BankKeeper, account sdk.AccAddress) error { + params := k.GetParams(ctx) + MaxSwapAmount := params.MaxSwapAmount + + for _, coin := range MaxSwapAmount { + denom := coin.Denom + randomAmount := simtypes.RandomAmount(r, sdkmath.NewInt(100000000000000)) + err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(denom, randomAmount))) + if err != nil { + return errors.New("unable to mint coins") + } + err = bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, account, sdk.NewCoins(sdk.NewCoin(denom, randomAmount))) + if err != nil { + return errors.New("unable to send coins") + } + } + return nil +} + +func randToken(r *rand.Rand, spendableCoin sdk.Coins) (sdk.Coin, error) { + if len(spendableCoin) == 0 { + return sdk.Coin{}, errors.New("insufficient funds") + } + token := spendableCoin[r.Intn(len(spendableCoin))] + randAmt, err := simtypes.RandPositiveInt(r, token.Amount.QuoRaw(4)) + if err != nil { + return sdk.Coin{}, errors.New("insufficient funds") + } + return sdk.NewCoin(token.Denom, randAmt), nil +} + func RandomSpendableToken(r *rand.Rand, spendableCoin sdk.Coins) sdk.Coin { token := spendableCoin[r.Intn(len(spendableCoin))] return sdk.NewCoin(token.Denom, simtypes.RandomAmount(r, token.Amount.QuoRaw(2))) @@ -488,33 +566,6 @@ func randBoolean(r *rand.Rand) bool { return r.Int()%2 == 0 } -// Double swap bill -func doubleSwapBill(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Keeper) (sdk.Coin, sdk.Coin, error) { - standardDenom, _ := k.GetStandardDenom(ctx) - param := k.GetParams(ctx) - - // generate sold standard Coin - lptDenom, _ := k.GetLptDenomFromDenoms(ctx, outputCoin.Denom, standardDenom) - reservePool, _ := k.GetPoolBalancesByLptDenom(ctx, lptDenom) - outputReserve := reservePool.AmountOf(outputCoin.Denom) - inputReserve := reservePool.AmountOf(standardDenom) - if outputCoin.Amount.GTE(outputReserve) { - return sdk.Coin{}, sdk.Coin{}, errorsmod.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("insufficient amount of %s, user expected: %s, actual: %s", outputCoin.Denom, outputCoin.Amount, outputReserve)) - } - soldStandardAmount := keeper.GetOutputPrice(outputCoin.Amount, inputReserve, outputReserve, param.Fee) - soldStandardCoin := sdk.NewCoin(standardDenom, soldStandardAmount) - - // generate input coin - lptDenom2, _ := k.GetLptDenomFromDenoms(ctx, soldStandardCoin.Denom, inputCoin.Denom) - reservePool2, _ := k.GetPoolBalancesByLptDenom(ctx, lptDenom2) - outputReserve2 := reservePool2.AmountOf(soldStandardCoin.Denom) - inputReserve2 := reservePool2.AmountOf(inputCoin.Denom) - soldTokenAmt := keeper.GetOutputPrice(soldStandardCoin.Amount, inputReserve2, outputReserve2, param.Fee) - inputCoin = sdk.NewCoin(inputCoin.Denom, soldTokenAmt) - - return inputCoin, outputCoin, nil -} - // A single swap bill func singleSwapBill(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Keeper) (sdk.Coin, sdk.Coin, error) { param := k.GetParams(ctx) @@ -524,30 +575,11 @@ func singleSwapBill(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Ke outputReserve := reservePool.AmountOf(outputCoin.Denom) inputReserve := reservePool.AmountOf(inputCoin.Denom) soldTokenAmt := keeper.GetOutputPrice(outputCoin.Amount, inputReserve, outputReserve, param.Fee) - inputCoin = sdk.NewCoin(inputCoin.Denom, soldTokenAmt) - - return inputCoin, outputCoin, nil -} -// Double swap sell orders -func doubleSwapSellOrder(inputCoin, outputCoin sdk.Coin, ctx sdk.Context, k keeper.Keeper) (sdk.Coin, sdk.Coin, error) { - standardDenom, _ := k.GetStandardDenom(ctx) - - param := k.GetParams(ctx) - - lptDenom, _ := k.GetLptDenomFromDenoms(ctx, inputCoin.Denom, standardDenom) - reservePool, _ := k.GetPoolBalancesByLptDenom(ctx, lptDenom) - inputReserve := reservePool.AmountOf(inputCoin.Denom) - outputReserve := reservePool.AmountOf(standardDenom) - standardAmount := keeper.GetInputPrice(inputCoin.Amount, inputReserve, outputReserve, param.Fee) - standardCoin := sdk.NewCoin(standardDenom, standardAmount) - - lptDenom2, _ := k.GetLptDenomFromDenoms(ctx, standardCoin.Denom, outputCoin.Denom) - reservePool2, _ := k.GetPoolBalancesByLptDenom(ctx, lptDenom2) - inputReserve2 := reservePool2.AmountOf(standardCoin.Denom) - outputReserve2 := reservePool2.AmountOf(outputCoin.Denom) - boughtTokenAmt := keeper.GetInputPrice(standardCoin.Amount, inputReserve2, outputReserve2, param.Fee) - outputCoin = sdk.NewCoin(outputCoin.Denom, boughtTokenAmt) + if soldTokenAmt.IsNegative() { + return sdk.Coin{}, sdk.Coin{}, errors.New("wrong token price calcualtion") + } + inputCoin = sdk.NewCoin(inputCoin.Denom, soldTokenAmt) return inputCoin, outputCoin, nil } From 542ac822dc52a0bd9d69bea1583696fc3fcc4d5c Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 25 Jul 2024 17:10:53 +0900 Subject: [PATCH 19/25] chore: make MintCoins to use variable amount instead of int value --- x/erc20/simulation/proposal.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/erc20/simulation/proposal.go b/x/erc20/simulation/proposal.go index ff8b9786..ed59b76d 100644 --- a/x/erc20/simulation/proposal.go +++ b/x/erc20/simulation/proposal.go @@ -89,10 +89,11 @@ func SimulateRegisterCoin(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account // mint cosmos coin to random accounts randomIteration := r.Intn(10) + mintAmt := sdkmath.NewInt(100000000) for i := 0; i < randomIteration; i++ { simAccount, _ := simtypes.RandomAcc(r, accs) - if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1000000000)))); err != nil { + if err := bk.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, mintAmt))); err != nil { return &types.MsgRegisterCoin{}, err } if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, simAccount.Address, sdk.NewCoins(sdk.NewCoin(coinMetadata.Base, sdkmath.NewInt(1000000000)))); err != nil { From 1089c9ba1776a79959f9662692aad73632178c03 Mon Sep 17 00:00:00 2001 From: poorphd Date: Thu, 25 Jul 2024 17:47:40 +0900 Subject: [PATCH 20/25] fix: panic on division by zero --- x/coinswap/simulation/operation.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/coinswap/simulation/operation.go b/x/coinswap/simulation/operation.go index eaf88be2..eae7be98 100644 --- a/x/coinswap/simulation/operation.go +++ b/x/coinswap/simulation/operation.go @@ -171,6 +171,9 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B } standardReserveAmt := balances.AmountOf(standardDenom) + if !standardReserveAmt.IsPositive() { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgAddLiquidity, "standardReserveAmt should be positive"), nil, err + } liquidity := bk.GetSupply(ctx, pool.LptDenom).Amount minLiquidity = liquidity.Mul(exactStandardAmt).Quo(standardReserveAmt) From ed9eef0ba1295c84989191f460f223f201c5e5af Mon Sep 17 00:00:00 2001 From: Byungchul Park <125338011+poorphd@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:03:17 +0900 Subject: [PATCH 21/25] fix: commnets Co-authored-by: Sujong Lee --- x/coinswap/types/msgs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/coinswap/types/msgs.go b/x/coinswap/types/msgs.go index ec84275e..ffc2e8d5 100644 --- a/x/coinswap/types/msgs.go +++ b/x/coinswap/types/msgs.go @@ -32,7 +32,7 @@ const ( // An exact coin has the senders desired buy or sell amount. // A calculated coin has the desired denomination and bounded amount // the sender is willing to buy or sell in this order. - +// // NewMsgSwapOrder creates a new MsgSwapOrder object. func NewMsgSwapOrder( input Input, From 6d15af1affea7c97c8007cfadfb6b59a0fa14439 Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 26 Jul 2024 16:52:28 +0900 Subject: [PATCH 22/25] feat: implement RegisterStoreDecoder for canto modules --- app/app.go | 8 ++++---- x/coinswap/module.go | 8 +++++++- x/csr/module.go | 13 +++++++++---- x/epochs/module.go | 4 +++- x/erc20/module.go | 11 +++++++++-- x/govshuttle/module.go | 10 ++++++++-- x/govshuttle/module_simulation.go | 4 +++- x/inflation/module.go | 14 +++++++++++--- 8 files changed, 54 insertions(+), 18 deletions(-) diff --git a/app/app.go b/app/app.go index fd82db17..2b86fd0c 100644 --- a/app/app.go +++ b/app/app.go @@ -758,12 +758,12 @@ func NewCanto( feemarket.NewAppModule(app.FeeMarketKeeper, feeMarketSs), // Canto app modules - inflation.NewAppModule(app.InflationKeeper, app.AccountKeeper, *app.StakingKeeper), - erc20.NewAppModule(app.Erc20Keeper, app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.FeeMarketKeeper, app.AccountKeeper.AddressCodec()), + inflation.NewAppModule(appCodec, app.InflationKeeper, app.AccountKeeper, *app.StakingKeeper), + erc20.NewAppModule(appCodec, app.Erc20Keeper, app.AccountKeeper, app.BankKeeper, app.EvmKeeper, app.FeeMarketKeeper, app.AccountKeeper.AddressCodec()), epochs.NewAppModule(appCodec, app.EpochsKeeper), onboarding.NewAppModule(*app.OnboardingKeeper), - govshuttle.NewAppModule(app.GovshuttleKeeper, app.AccountKeeper, app.AccountKeeper.AddressCodec()), - csr.NewAppModule(app.CSRKeeper, app.AccountKeeper), + govshuttle.NewAppModule(appCodec, app.GovshuttleKeeper, app.AccountKeeper, app.AccountKeeper.AddressCodec()), + csr.NewAppModule(appCodec, app.CSRKeeper, app.AccountKeeper), coinswap.NewAppModule(appCodec, app.CoinswapKeeper, app.AccountKeeper, app.BankKeeper), ) diff --git a/x/coinswap/module.go b/x/coinswap/module.go index bd726022..59916e5c 100644 --- a/x/coinswap/module.go +++ b/x/coinswap/module.go @@ -39,6 +39,11 @@ type AppModuleBasic struct { cdc codec.Codec } +// NewAppModuleBasic return a new AppModuleBasic +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + // Name returns the coinswap module's name. func (AppModuleBasic) Name() string { return types.ModuleName } @@ -99,7 +104,7 @@ type AppModule struct { // NewAppModule creates a new AppModule object func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{cdc: cdc}, + AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, accountKeeper: accountKeeper, bankKeeper: bankKeeper, @@ -163,6 +168,7 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight // RegisterStoreDecoder registers a decoder for coinswap module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) } // WeightedOperations returns the all the coinswap module operations with their respective weights. diff --git a/x/csr/module.go b/x/csr/module.go index 885573a3..7663cdcb 100644 --- a/x/csr/module.go +++ b/x/csr/module.go @@ -7,7 +7,6 @@ import ( // this line is used by starport scaffolding # 1 - "github.com/Canto-Network/Canto/v7/x/coinswap/simulation" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -23,6 +22,7 @@ import ( "github.com/Canto-Network/Canto/v7/x/csr/client/cli" "github.com/Canto-Network/Canto/v7/x/csr/keeper" + "github.com/Canto-Network/Canto/v7/x/csr/simulation" "github.com/Canto-Network/Canto/v7/x/csr/types" ) @@ -42,10 +42,10 @@ var ( // AppModuleBasic implements the AppModuleBasic interface for the csr module. type AppModuleBasic struct { - cdc codec.BinaryCodec + cdc codec.Codec } -func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { return AppModuleBasic{cdc: cdc} } @@ -107,11 +107,12 @@ type AppModule struct { } func NewAppModule( + cdc codec.Codec, keeper keeper.Keeper, acctKeeper authkeeper.AccountKeeper, ) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{}, + AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, accountKeeper: acctKeeper, } @@ -182,3 +183,7 @@ func (am AppModule) BeginBlock(ctx context.Context) error { func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { return simulation.ProposalMsgs() } + +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.ModuleName] = simulation.NewDecodeStore(am.cdc) +} diff --git a/x/epochs/module.go b/x/epochs/module.go index f1fa22d7..7ff2157c 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -22,6 +22,7 @@ import ( "github.com/Canto-Network/Canto/v7/x/epochs/client/cli" "github.com/Canto-Network/Canto/v7/x/epochs/keeper" + "github.com/Canto-Network/Canto/v7/x/epochs/simulation" "github.com/Canto-Network/Canto/v7/x/epochs/types" ) @@ -170,8 +171,9 @@ func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.We return []simtypes.WeightedProposalContent{} } -// RegisterStoreDecoder registers a decoder for supply module's types +// RegisterStoreDecoder registers a decoder for epoch module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.ModuleName] = simulation.NewDecodeStore(am.cdc) } // WeightedOperations returns the all the gov module operations with their respective weights. diff --git a/x/erc20/module.go b/x/erc20/module.go index b023079e..42890467 100644 --- a/x/erc20/module.go +++ b/x/erc20/module.go @@ -37,7 +37,12 @@ var ( // app module Basics object type AppModuleBasic struct { - ac address.Codec + ac address.Codec + cdc codec.Codec +} + +func NewAppModuleBasic(ac address.Codec, cdc codec.Codec) AppModuleBasic { + return AppModuleBasic{ac: ac, cdc: cdc} } func (AppModuleBasic) Name() string { @@ -102,6 +107,7 @@ type AppModule struct { // NewAppModule creates a new AppModule Object func NewAppModule( + cdc codec.Codec, k keeper.Keeper, ak authkeeper.AccountKeeper, bk types.BankKeeper, @@ -110,7 +116,7 @@ func NewAppModule( ac address.Codec, ) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{ac: ac}, + AppModuleBasic: AppModuleBasic{ac: ac, cdc: cdc}, keeper: k, ak: ak, bk: bk, @@ -172,6 +178,7 @@ func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes } func (am AppModule) RegisterStoreDecoder(decoderRegistry simtypes.StoreDecoderRegistry) { + decoderRegistry[types.ModuleName] = simulation.NewDecodeStore(am.cdc) } func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { diff --git a/x/govshuttle/module.go b/x/govshuttle/module.go index 60bf666a..a66a84a9 100644 --- a/x/govshuttle/module.go +++ b/x/govshuttle/module.go @@ -42,7 +42,12 @@ var ( // AppModule implements the sdk.AppModuleBasic interface. type AppModuleBasic struct { - ac address.Codec + ac address.Codec + cdc codec.Codec +} + +func NewAppModuleBasic(ac address.Codec, cdc codec.Codec) AppModuleBasic { + return AppModuleBasic{ac: ac, cdc: cdc} } // Name returns the capability module's name. @@ -105,12 +110,13 @@ type AppModule struct { } func NewAppModule( + cdc codec.Codec, k keeper.Keeper, ak authkeeper.AccountKeeper, ac address.Codec, ) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{ac: ac}, + AppModuleBasic: AppModuleBasic{ac: ac, cdc: cdc}, keeper: k, accountkeeper: ak, } diff --git a/x/govshuttle/module_simulation.go b/x/govshuttle/module_simulation.go index cfe1bb57..80d8dc8a 100644 --- a/x/govshuttle/module_simulation.go +++ b/x/govshuttle/module_simulation.go @@ -43,7 +43,9 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.ModuleName] = govshuttlesimulation.NewDecodeStore(am.cdc) +} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { diff --git a/x/inflation/module.go b/x/inflation/module.go index e3564598..e1842acf 100644 --- a/x/inflation/module.go +++ b/x/inflation/module.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/Canto-Network/Canto/v7/x/inflation/simulation" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -23,6 +22,7 @@ import ( "github.com/Canto-Network/Canto/v7/x/inflation/client/cli" "github.com/Canto-Network/Canto/v7/x/inflation/keeper" + "github.com/Canto-Network/Canto/v7/x/inflation/simulation" "github.com/Canto-Network/Canto/v7/x/inflation/types" ) @@ -37,7 +37,13 @@ var ( ) // app module Basics object -type AppModuleBasic struct{} +type AppModuleBasic struct { + cdc codec.Codec +} + +func NewAppModuleBasic(cdc codec.Codec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} // Name returns the inflation module's name. func (AppModuleBasic) Name() string { @@ -105,12 +111,13 @@ type AppModule struct { // NewAppModule creates a new AppModule Object func NewAppModule( + cdc codec.Codec, k keeper.Keeper, ak authkeeper.AccountKeeper, sk stakingkeeper.Keeper, ) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{}, + AppModuleBasic: NewAppModuleBasic(cdc), keeper: k, ak: ak, sk: sk, @@ -186,6 +193,7 @@ func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.Weight // RegisterStoreDecoder registers a decoder for inflation module's types. func (am AppModule) RegisterStoreDecoder(decoderRegistry simtypes.StoreDecoderRegistry) { + decoderRegistry[types.ModuleName] = simulation.NewDecodeStore(am.cdc) } // WeightedOperations doesn't return any inflation module operation. From 096d7d1e1fabad31a9d9ce612dfc7f8729b08f2d Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 26 Jul 2024 16:57:01 +0900 Subject: [PATCH 23/25] chore: interface chack for module.AppModuleSimulation --- x/csr/module.go | 15 +++++++++++---- x/epochs/module.go | 9 +++++---- x/erc20/module.go | 9 +++++---- x/inflation/module.go | 9 +++++---- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/x/csr/module.go b/x/csr/module.go index 7663cdcb..985d4ca4 100644 --- a/x/csr/module.go +++ b/x/csr/module.go @@ -27,10 +27,11 @@ import ( ) var ( - _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleBasic = AppModule{} - _ module.HasServices = AppModule{} - _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = AppModule{} + _ module.HasServices = AppModule{} + _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleSimulation = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasBeginBlocker = AppModule{} @@ -106,6 +107,12 @@ type AppModule struct { keeper keeper.Keeper } +func (am AppModule) GenerateGenesisState(input *module.SimulationState) {} + +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return []simtypes.WeightedOperation{} +} + func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, diff --git a/x/epochs/module.go b/x/epochs/module.go index 7ff2157c..c283da5d 100644 --- a/x/epochs/module.go +++ b/x/epochs/module.go @@ -27,10 +27,11 @@ import ( ) var ( - _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleBasic = AppModule{} - _ module.HasServices = AppModule{} - _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = AppModule{} + _ module.HasServices = AppModule{} + _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleSimulation = AppModule{} _ appmodule.AppModule = AppModule{} _ appmodule.HasBeginBlocker = AppModule{} diff --git a/x/erc20/module.go b/x/erc20/module.go index 42890467..0305b256 100644 --- a/x/erc20/module.go +++ b/x/erc20/module.go @@ -27,10 +27,11 @@ import ( // type check to ensure the interface is properly implemented var ( - _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleBasic = AppModule{} - _ module.HasServices = AppModule{} - _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = AppModule{} + _ module.HasServices = AppModule{} + _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleSimulation = AppModule{} _ appmodule.AppModule = AppModule{} ) diff --git a/x/inflation/module.go b/x/inflation/module.go index e1842acf..cc8443e9 100644 --- a/x/inflation/module.go +++ b/x/inflation/module.go @@ -28,10 +28,11 @@ import ( // type check to ensure the interface is properly implemented var ( - _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleBasic = AppModule{} - _ module.HasServices = AppModule{} - _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleBasic = AppModule{} + _ module.HasServices = AppModule{} + _ module.HasABCIGenesis = AppModule{} + _ module.AppModuleSimulation = AppModule{} _ appmodule.AppModule = AppModule{} ) From 332ff06208e48f9396c8b9b53ea1439f71d9fa42 Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 26 Jul 2024 17:18:05 +0900 Subject: [PATCH 24/25] fix: add liquidity operation --- x/coinswap/simulation/operation.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/x/coinswap/simulation/operation.go b/x/coinswap/simulation/operation.go index eae7be98..40f86efa 100644 --- a/x/coinswap/simulation/operation.go +++ b/x/coinswap/simulation/operation.go @@ -114,6 +114,14 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B "standardAmount should be positive", ), nil, nil } + params := k.GetParams(ctx) + if exactStandardAmt.GTE(params.MaxStandardCoinPerPool) { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "standardAmount should be less than MaxStandardCoinPerPool", + ), nil, nil + } maxToken, err = randToken(r, spendable) if err != nil { @@ -148,6 +156,25 @@ func SimulateMsgAddLiquidity(k keeper.Keeper, ak types.AccountKeeper, bk types.B ), nil, err } + // check maxToken is registered in MaxSwapAmount + found := func(denom string) bool { + MaxSwapAmount := params.MaxSwapAmount + for _, coin := range MaxSwapAmount { + if coin.Denom == denom { + return true + } + } + return false + }(maxToken.Denom) + + if !found { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgAddLiquidity, + "maxToken is not registered in MaxSwapAmount", + ), nil, err + } + poolID := types.GetPoolId(maxToken.Denom) pool, has := k.GetPool(ctx, poolID) if !has { From 1c5079b4b7460a4b6c5cec456fbb73124fd77eaa Mon Sep 17 00:00:00 2001 From: poorphd Date: Fri, 26 Jul 2024 17:29:22 +0900 Subject: [PATCH 25/25] fix: swap operation --- x/coinswap/simulation/operation.go | 45 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/x/coinswap/simulation/operation.go b/x/coinswap/simulation/operation.go index 40f86efa..ab1240e2 100644 --- a/x/coinswap/simulation/operation.go +++ b/x/coinswap/simulation/operation.go @@ -273,6 +273,23 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank isBuyOrder bool ) + simAccount, _ := simtypes.RandomAcc(r, accs) + err = FundAccount(r, ctx, k, bk, simAccount.Address) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to fund account"), nil, err + } + account := ak.GetAccount(ctx, simAccount.Address) + spendable := bk.SpendableCoins(ctx, account.GetAddress()) + standardDenom, _ := k.GetStandardDenom(ctx) + + if spendable.IsZero() { + return simtypes.NoOpMsg( + types.ModuleName, + TypeMsgSwapOrder, + "spendable is zero", + ), nil, err + } + pools := k.GetAllPools(ctx) if len(pools) == 0 { return simtypes.NoOpMsg( @@ -284,21 +301,20 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank pool := pools[r.Intn(len(pools))] - simAccount, _ := simtypes.RandomAcc(r, accs) - err = FundAccount(r, ctx, k, bk, simAccount.Address) + reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, TypeMsgSwapOrder, "unable to fund account"), nil, err + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRemoveLiquidity, "inputCoin should exist in the pool"), nil, nil } - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - standardDenom, _ := k.GetStandardDenom(ctx) - if spendable.IsZero() { + standardReserveAmt := reservePool.AmountOf(standardDenom) + tokenReserveAmt := reservePool.AmountOf(pool.CounterpartyDenom) + + if !standardReserveAmt.IsPositive() || !tokenReserveAmt.IsPositive() { return simtypes.NoOpMsg( types.ModuleName, TypeMsgSwapOrder, - "spendable is zero", - ), nil, err + "reserve pool should be positive", + ), nil, nil } // sold coin @@ -354,17 +370,6 @@ func SimulateMsgSwapOrder(k keeper.Keeper, ak types.AccountKeeper, bk types.Bank } } - reservePool, err := k.GetPoolBalancesByLptDenom(ctx, pool.LptDenom) - standardReserveAmt := reservePool.AmountOf(standardDenom) - tokenReserveAmt := reservePool.AmountOf(pool.CounterpartyDenom) - if !standardReserveAmt.IsPositive() || !tokenReserveAmt.IsPositive() { - return simtypes.NoOpMsg( - types.ModuleName, - TypeMsgSwapOrder, - "reserve pool should be positive", - ), nil, nil - } - if !outputCoin.Amount.IsPositive() { return simtypes.NoOpMsg( types.ModuleName,