diff --git a/app/app_testing/test_helpers.go b/app/app_testing/test_helpers.go index 0a081c64..61e3c24f 100644 --- a/app/app_testing/test_helpers.go +++ b/app/app_testing/test_helpers.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/terra-money/core/v2/app" terra_app "github.com/terra-money/core/v2/app" + appparams "github.com/terra-money/core/v2/app/params" terrraParams "github.com/terra-money/core/v2/app/params" "github.com/terra-money/core/v2/app/wasmconfig" tokenfactorytypes "github.com/terra-money/core/v2/x/tokenfactory/types" @@ -42,6 +43,7 @@ type AppTestSuite struct { // Setup sets up basic environment for suite (App, Ctx, and test accounts) func (s *AppTestSuite) Setup() { + appparams.RegisterAddressesConfig() baseTestAccts := CreateRandomAccounts(3) encCfg := terra_app.MakeEncodingConfig() @@ -72,6 +74,18 @@ func (s *AppTestSuite) Setup() { s.App.DistrKeeper.SetFeePool(s.Ctx, distrtypes.InitialFeePool()) } +func (s *AppTestSuite) AssertEventEmitted(ctx sdk.Context, eventTypeExpected string, numEventsExpected int) { + allEvents := ctx.EventManager().Events() + // filter out other events + actualEvents := make([]sdk.Event, 0) + for _, event := range allEvents { + if event.Type == eventTypeExpected { + actualEvents = append(actualEvents, event) + } + } + s.Require().Equal(numEventsExpected, len(actualEvents)) +} + // CreateRandomAccounts is a function return a list of randomly generated AccAddresses func CreateRandomAccounts(numAccts int) []sdk.AccAddress { testAddrs := make([]sdk.AccAddress, numAccts) diff --git a/x/tokenfactory/client/cli/query_test.go b/x/tokenfactory/client/cli/query_test.go new file mode 100644 index 00000000..e9be3147 --- /dev/null +++ b/x/tokenfactory/client/cli/query_test.go @@ -0,0 +1,66 @@ +package cli_test + +import ( + gocontext "context" + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/terra-money/core/v2/app/app_testing" + "github.com/terra-money/core/v2/app/config" + "github.com/terra-money/core/v2/x/tokenfactory/types" +) + +type QueryTestSuite struct { + app_testing.AppTestSuite +} + +func (s *QueryTestSuite) TestQueriesNeverAlterState() { + s.Setup() + + // fund acc + fundAccsAmount := sdk.NewCoins(sdk.NewCoin(config.BondDenom, math.NewInt(1_000_000_000))) + s.FundAcc(s.TestAccs[0], fundAccsAmount) + // create new token + _, err := s.App.TokenFactoryKeeper.CreateDenom(s.Ctx, s.TestAccs[0].String(), "tokenfactory") + s.Require().NoError(err) + + testCases := []struct { + name string + query string + input interface{} + output interface{} + }{ + { + "Query denom authority metadata", + "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", + &types.QueryDenomAuthorityMetadataRequest{Denom: "tokenfactory"}, + &types.QueryDenomAuthorityMetadataResponse{}, + }, + { + "Query denoms by creator", + "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", + &types.QueryDenomsFromCreatorRequest{Creator: s.TestAccs[0].String()}, + &types.QueryDenomsFromCreatorResponse{}, + }, + { + "Query params", + "/osmosis.tokenfactory.v1beta1.Query/Params", + &types.QueryParamsRequest{}, + &types.QueryParamsResponse{}, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + err := s.QueryHelper.Invoke(gocontext.Background(), tc.query, tc.input, tc.output) + s.Require().NoError(err) + }) + } +} + +func TestQueryTestSuite(t *testing.T) { + suite.Run(t, new(QueryTestSuite)) +} diff --git a/x/tokenfactory/keeper/before_send_test.go b/x/tokenfactory/keeper/before_send_test.go index b7d35b49..1d11c185 100644 --- a/x/tokenfactory/keeper/before_send_test.go +++ b/x/tokenfactory/keeper/before_send_test.go @@ -96,8 +96,6 @@ func (s *KeeperTestSuite) TestBeforeSendHook() { // mint enough coins to the creator _, err = s.msgServer.Mint(sdk.WrapSDKContext(s.Ctx), types.NewMsgMint(s.TestAccs[0].String(), sdk.NewInt64Coin(denom, 1000000000))) s.Require().NoError(err) - // mint some non token factory denom coins for testing - s.FundAcc(sdk.AccAddress(s.TestAccs[0].String()), sdk.Coins{sdk.NewInt64Coin("foo", 100000000000)}) // set beforesend hook to the new denom _, err = s.msgServer.SetBeforeSendHook(sdk.WrapSDKContext(s.Ctx), types.NewMsgSetBeforeSendHook(s.TestAccs[0].String(), denom, cosmwasmAddress.String())) diff --git a/x/tokenfactory/keeper/createdenom_test.go b/x/tokenfactory/keeper/createdenom_test.go new file mode 100644 index 00000000..9c275127 --- /dev/null +++ b/x/tokenfactory/keeper/createdenom_test.go @@ -0,0 +1,242 @@ +package keeper_test + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/terra-money/core/v2/app/config" + "github.com/terra-money/core/v2/x/tokenfactory/types" +) + +func (s *KeeperTestSuite) TestMsgCreateDenom() { + var ( + tokenFactoryKeeper = s.App.TokenFactoryKeeper + bankKeeper = s.App.BankKeeper + denomCreationFee = sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(1000000))) + ) + + // Set the denom creation fee. It is currently turned off in favor + // of gas charge by default. + params := s.App.TokenFactoryKeeper.GetParams(s.Ctx) + params.DenomCreationFee = denomCreationFee + s.App.TokenFactoryKeeper.SetParams(s.Ctx, params) + + // Fund denom creation fee for every execution of MsgCreateDenom. + s.FundAcc(s.TestAccs[0], denomCreationFee) + s.FundAcc(s.TestAccs[0], denomCreationFee) + s.FundAcc(s.TestAccs[1], denomCreationFee) + + // Get balance of acc 0 before creating a denom + preCreateBalance := bankKeeper.GetBalance(s.Ctx, s.TestAccs[0], denomCreationFee[0].Denom) + + // Creating a denom should work + res, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + s.Require().NoError(err) + s.Require().NotEmpty(res.GetNewTokenDenom()) + + // Make sure that the admin is set correctly + queryRes, err := s.queryClient.DenomAuthorityMetadata(s.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: res.GetNewTokenDenom(), + }) + s.Require().NoError(err) + s.Require().Equal(s.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) + + // Make sure that creation fee was deducted + postCreateBalance := bankKeeper.GetBalance(s.Ctx, s.TestAccs[0], tokenFactoryKeeper.GetParams(s.Ctx).DenomCreationFee[0].Denom) + s.Require().True(preCreateBalance.Sub(postCreateBalance).IsEqual(denomCreationFee[0])) + + // Make sure that a second version of the same denom can't be recreated + _, err = s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + s.Require().Error(err) + + // Creating a second denom should work + res, err = s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "litecoin")) + s.Require().NoError(err) + s.Require().NotEmpty(res.GetNewTokenDenom()) + + // Try querying all the denoms created by s.TestAccs[0] + queryRes2, err := s.queryClient.DenomsFromCreator(s.Ctx.Context(), &types.QueryDenomsFromCreatorRequest{ + Creator: s.TestAccs[0].String(), + }) + s.Require().NoError(err) + s.Require().Len(queryRes2.Denoms, 2) + + // Make sure that a second account can create a denom with the same subdenom + res, err = s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[1].String(), "bitcoin")) + s.Require().NoError(err) + s.Require().NotEmpty(res.GetNewTokenDenom()) + + // Make sure that an address with a "/" in it can't create denoms + _, err = s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom("w.eth/creator", "bitcoin")) + s.Require().Error(err) +} + +func (s *KeeperTestSuite) TestCreateDenom() { + var ( + defaultDenomCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(config.BondDenom, sdk.NewInt(50000000)))} + nilCreationFee = types.Params{DenomCreationFee: nil} + largeCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(config.BondDenom, sdk.NewInt(5000000000)))} + ) + + for _, tc := range []struct { + desc string + denomCreationFee types.Params + setup func() + subdenom string + valid bool + }{ + { + desc: "subdenom too long", + denomCreationFee: defaultDenomCreationFee, + subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", + valid: false, + }, + { + desc: "subdenom and creator pair already exists", + denomCreationFee: defaultDenomCreationFee, + setup: func() { + _, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + s.Require().NoError(err) + }, + subdenom: "bitcoin", + valid: false, + }, + { + desc: "success case: defaultDenomCreationFee", + denomCreationFee: defaultDenomCreationFee, + subdenom: "evmos", + valid: true, + }, + { + desc: "success case: nilCreationFee", + denomCreationFee: nilCreationFee, + subdenom: "ucoin", + valid: true, + }, + { + desc: "account doesn't have enough to pay for denom creation fee", + denomCreationFee: largeCreationFee, + subdenom: "tooexpensive", + valid: false, + }, + { + desc: "subdenom having invalid characters", + denomCreationFee: defaultDenomCreationFee, + subdenom: "bit/***///&&&/coin", + valid: false, + }, + } { + s.SetupTest() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + if tc.setup != nil { + tc.setup() + } + tokenFactoryKeeper := s.App.TokenFactoryKeeper + bankKeeper := s.App.BankKeeper + // Set denom creation fee in params + s.FundAcc(s.TestAccs[0], defaultDenomCreationFee.DenomCreationFee) + tokenFactoryKeeper.SetParams(s.Ctx, tc.denomCreationFee) + denomCreationFee := tokenFactoryKeeper.GetParams(s.Ctx).DenomCreationFee + s.Require().Equal(tc.denomCreationFee.DenomCreationFee, denomCreationFee) + + // note balance, create a tokenfactory denom, then note balance again + preCreateBalance := bankKeeper.GetAllBalances(s.Ctx, s.TestAccs[0]) + res, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), tc.subdenom)) + postCreateBalance := bankKeeper.GetAllBalances(s.Ctx, s.TestAccs[0]) + if tc.valid { + s.Require().NoError(err) + s.Require().True(preCreateBalance.Sub(postCreateBalance[0]).IsEqual(denomCreationFee)) + + // Make sure that the admin is set correctly + queryRes, err := s.queryClient.DenomAuthorityMetadata(s.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: res.GetNewTokenDenom(), + }) + + s.Require().NoError(err) + s.Require().Equal(s.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) + + // Make sure that the denom metadata is initialized correctly + metadata, found := bankKeeper.GetDenomMetaData(s.Ctx, res.GetNewTokenDenom()) + s.Require().True(found) + s.Require().Equal(banktypes.Metadata{ + DenomUnits: []*banktypes.DenomUnit{{ + Denom: res.GetNewTokenDenom(), + Exponent: 0, + }}, + Base: res.GetNewTokenDenom(), + }, metadata) + } else { + s.Require().Error(err) + // Ensure we don't charge if we expect an error + s.Require().True(preCreateBalance.IsEqual(postCreateBalance)) + } + }) + } +} + +func (s *KeeperTestSuite) TestGasConsume() { + // It's hard to estimate exactly how much gas will be consumed when creating a + // denom, because besides consuming the gas specified by the params, the keeper + // also does a bunch of other things that consume gas. + // + // Rather, we test whether the gas consumed is within a range. Specifically, + // the range [gasConsume, gasConsume + offset]. If the actual gas consumption + // falls within the range for all test cases, we consider the test passed. + // + // In experience, the total amount of gas consumed should consume be ~30k more + // than the set amount. + const offset = 50000 + + for _, tc := range []struct { + desc string + gasConsume uint64 + }{ + { + desc: "gas consume zero", + gasConsume: 0, + }, + { + desc: "gas consume 1,000,000", + gasConsume: 1_000_000, + }, + { + desc: "gas consume 10,000,000", + gasConsume: 10_000_000, + }, + { + desc: "gas consume 25,000,000", + gasConsume: 25_000_000, + }, + { + desc: "gas consume 50,000,000", + gasConsume: 50_000_000, + }, + { + desc: "gas consume 200,000,000", + gasConsume: 200_000_000, + }, + } { + s.SetupTest() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // set params with the gas consume amount + s.App.TokenFactoryKeeper.SetParams(s.Ctx, types.NewParams(nil, tc.gasConsume)) + + // amount of gas consumed prior to the denom creation + gasConsumedBefore := s.Ctx.GasMeter().GasConsumed() + + // create a denom + _, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "larry")) + s.Require().NoError(err) + + // amount of gas consumed after the denom creation + gasConsumedAfter := s.Ctx.GasMeter().GasConsumed() + + // the amount of gas consumed must be within the range + gasConsumed := gasConsumedAfter - gasConsumedBefore + s.Require().Greater(gasConsumed, tc.gasConsume) + s.Require().Less(gasConsumed, tc.gasConsume+offset) + }) + } +} diff --git a/x/tokenfactory/keeper/genesis_test.go b/x/tokenfactory/keeper/genesis_test.go new file mode 100644 index 00000000..2739bd94 --- /dev/null +++ b/x/tokenfactory/keeper/genesis_test.go @@ -0,0 +1,68 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/terra-money/core/v2/x/tokenfactory/types" +) + +func (s *KeeperTestSuite) TestGenesis() { + genesisState := types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/terra13s4gwzxv6dycfctvddfuy6r3zm7d6zklynzzj5/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "terra13s4gwzxv6dycfctvddfuy6r3zm7d6zklynzzj5", + }, + }, + { + Denom: "factory/terra13s4gwzxv6dycfctvddfuy6r3zm7d6zklynzzj5/diff-admin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "terra16jpsrgl423fqg6n0e9edllew9z0gm7rhl5300u", + }, + }, + { + Denom: "factory/terra13s4gwzxv6dycfctvddfuy6r3zm7d6zklynzzj5/litecoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "terra13s4gwzxv6dycfctvddfuy6r3zm7d6zklynzzj5", + }, + }, + }, + } + + app := s.App + + // Test both with bank denom metadata set, and not set. + for i, denom := range genesisState.FactoryDenoms { + // hacky, sets bank metadata to exist if i != 0, to cover both cases. + if i != 0 { + app.BankKeeper.SetDenomMetaData(s.Ctx, banktypes.Metadata{Base: denom.GetDenom(), Display: "test"}) + } + } + + // check before initGenesis that the module account is nil + tokenfactoryModuleAccount := app.AccountKeeper.GetAccount(s.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + s.Require().Nil(tokenfactoryModuleAccount) + + app.TokenFactoryKeeper.SetParams(s.Ctx, types.Params{DenomCreationFee: sdk.Coins{sdk.NewInt64Coin("uosmo", 100)}}) + app.TokenFactoryKeeper.InitGenesis(s.Ctx, genesisState) + + // check that the module account is now initialized + tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(s.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + s.Require().NotNil(tokenfactoryModuleAccount) + + exportedGenesis := app.TokenFactoryKeeper.ExportGenesis(s.Ctx) + s.Require().NotNil(exportedGenesis) + s.Require().Equal(genesisState, *exportedGenesis) + + app.BankKeeper.SetParams(s.Ctx, banktypes.DefaultParams()) + app.BankKeeper.InitGenesis(s.Ctx, app.BankKeeper.ExportGenesis(s.Ctx)) + for i, denom := range genesisState.FactoryDenoms { + // hacky, check whether bank metadata is not replaced if i != 0, to cover both cases. + if i != 0 { + metadata, found := app.BankKeeper.GetDenomMetaData(s.Ctx, denom.GetDenom()) + s.Require().True(found) + s.Require().Equal(metadata, banktypes.Metadata{Base: denom.GetDenom(), Display: "test"}) + } + } +} diff --git a/x/tokenfactory/keeper/msg_server_test.go b/x/tokenfactory/keeper/msg_server_test.go new file mode 100644 index 00000000..00aec0c9 --- /dev/null +++ b/x/tokenfactory/keeper/msg_server_test.go @@ -0,0 +1,274 @@ +package keeper_test + +import ( + "fmt" + + "github.com/terra-money/core/v2/x/tokenfactory/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// TestMintDenomMsg tests TypeMsgMint message is emitted on a successful mint +func (s *KeeperTestSuite) TestMintDenomMsg() { + // Create a denom + res, _ := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + defaultDenom := res.GetNewTokenDenom() + + for _, tc := range []struct { + desc string + amount int64 + mintDenom string + admin string + valid bool + expectedMessageEvents int + }{ + { + desc: "denom does not exist", + amount: 10, + mintDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: s.TestAccs[0].String(), + valid: false, + }, + { + desc: "success case", + amount: 10, + mintDenom: defaultDenom, + admin: s.TestAccs[0].String(), + valid: true, + expectedMessageEvents: 1, + }, + } { + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Require().Equal(0, len(ctx.EventManager().Events())) + // Test mint message + _, err := s.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) + if tc.valid { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + // Ensure current number and type of event is emitted + s.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents) + }) + } +} + +// TestBurnDenomMsg tests TypeMsgBurn message is emitted on a successful burn +func (s *KeeperTestSuite) TestBurnDenomMsg() { + // Create a denom + res, _ := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + defaultDenom := res.GetNewTokenDenom() + + // mint 10 default token for testAcc[0] + _, err := s.msgServer.Mint(sdk.WrapSDKContext(s.Ctx), types.NewMsgMint(s.TestAccs[0].String(), sdk.NewInt64Coin(defaultDenom, 10))) + s.Require().NoError(err) + + for _, tc := range []struct { + desc string + amount int64 + burnDenom string + admin string + valid bool + expectedMessageEvents int + }{ + { + desc: "denom does not exist", + burnDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: s.TestAccs[0].String(), + valid: false, + }, + { + desc: "success case", + burnDenom: defaultDenom, + admin: s.TestAccs[0].String(), + valid: true, + expectedMessageEvents: 1, + }, + } { + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Require().Equal(0, len(ctx.EventManager().Events())) + // Test burn message + _, err := s.msgServer.Burn(sdk.WrapSDKContext(ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) + if tc.valid { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + // Ensure current number and type of event is emitted + s.AssertEventEmitted(ctx, types.TypeMsgBurn, tc.expectedMessageEvents) + }) + } +} + +// TestCreateDenomMsg tests TypeMsgCreateDenom message is emitted on a successful denom creation +func (s *KeeperTestSuite) TestCreateDenomMsg() { + for _, tc := range []struct { + desc string + subdenom string + valid bool + expectedMessageEvents int + }{ + { + desc: "subdenom too long", + subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", + valid: false, + }, + { + desc: "success case: defaultDenomCreationFee", + subdenom: "evmos", + valid: true, + expectedMessageEvents: 1, + }, + } { + s.SetupTest() + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Require().Equal(0, len(ctx.EventManager().Events())) + // Set denom creation fee in params + // Test create denom message + _, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), tc.subdenom)) + if tc.valid { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + // Ensure current number and type of event is emitted + s.AssertEventEmitted(ctx, types.TypeMsgCreateDenom, tc.expectedMessageEvents) + }) + } +} + +// TestChangeAdminDenomMsg tests TypeMsgChangeAdmin message is emitted on a successful admin change +func (s *KeeperTestSuite) TestChangeAdminDenomMsg() { + for _, tc := range []struct { + desc string + msgChangeAdmin func(denom string) *types.MsgChangeAdmin + expectedChangeAdminPass bool + expectedAdminIndex int + msgMint func(denom string) *types.MsgMint + expectedMintPass bool + expectedMessageEvents int + }{ + { + desc: "non-admins can't change the existing admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(s.TestAccs[1].String(), denom, s.TestAccs[2].String()) + }, + expectedChangeAdminPass: false, + expectedAdminIndex: 0, + }, + { + desc: "success change admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(s.TestAccs[0].String(), denom, s.TestAccs[1].String()) + }, + expectedAdminIndex: 1, + expectedChangeAdminPass: true, + expectedMessageEvents: 1, + msgMint: func(denom string) *types.MsgMint { + return types.NewMsgMint(s.TestAccs[1].String(), sdk.NewInt64Coin(denom, 5)) + }, + expectedMintPass: true, + }, + } { + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // setup test + s.SetupTest() + ctx := s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Require().Equal(0, len(ctx.EventManager().Events())) + // Create a denom and mint + res, err := s.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + s.Require().NoError(err) + testDenom := res.GetNewTokenDenom() + _, err = s.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(s.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) + s.Require().NoError(err) + // Test change admin message + _, err = s.msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), tc.msgChangeAdmin(testDenom)) + if tc.expectedChangeAdminPass { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + // Ensure current number and type of event is emitted + s.AssertEventEmitted(ctx, types.TypeMsgChangeAdmin, tc.expectedMessageEvents) + }) + } +} + +// TestSetDenomMetaDataMsg tests TypeMsgSetDenomMetadata message is emitted on a successful denom metadata change +func (s *KeeperTestSuite) TestSetDenomMetaDataMsg() { + // setup test + s.SetupTest() + + // Create a denom + res, _ := s.msgServer.CreateDenom(sdk.WrapSDKContext(s.Ctx), types.NewMsgCreateDenom(s.TestAccs[0].String(), "bitcoin")) + defaultDenom := res.GetNewTokenDenom() + + for _, tc := range []struct { + desc string + msgSetDenomMetadata types.MsgSetDenomMetadata + expectedPass bool + expectedMessageEvents int + }{ + { + desc: "successful set denom metadata", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(s.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: defaultDenom, + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: defaultDenom, + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: true, + expectedMessageEvents: 1, + }, + { + desc: "non existent factory denom name", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(s.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: fmt.Sprintf("factory/%s/litecoin", s.TestAccs[0].String()), + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: fmt.Sprintf("factory/%s/litecoin", s.TestAccs[0].String()), + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + } { + s.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Require().Equal(0, len(ctx.EventManager().Events())) + // Test set denom metadata message + _, err := s.msgServer.SetDenomMetadata(sdk.WrapSDKContext(ctx), &tc.msgSetDenomMetadata) + if tc.expectedPass { + s.Require().NoError(err) + } else { + s.Require().Error(err) + } + // Ensure current number and type of event is emitted + s.AssertEventEmitted(ctx, types.TypeMsgSetDenomMetadata, tc.expectedMessageEvents) + }) + } +}