diff --git a/wasmbinding/bindings/msg.go b/wasmbinding/bindings/msg.go index a3d03ecca..1ab909827 100644 --- a/wasmbinding/bindings/msg.go +++ b/wasmbinding/bindings/msg.go @@ -5,6 +5,7 @@ import ( "cosmossdk.io/math" cosmostypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" paramChange "github.com/cosmos/cosmos-sdk/x/params/types/proposal" feetypes "github.com/neutron-org/neutron/v2/x/feerefunder/types" @@ -44,9 +45,14 @@ type NeutronMsg struct { /// Currently, the burn from address must be the admin contract. BurnTokens *BurnTokens `json:"burn_tokens,omitempty"` /// Contracts can set before send hook for an existing factory denom - // that they are the admin of. - // Currently, the set before hook call should be performed from address that must be the admin contract. + /// that they are the admin of. + /// Currently, the set before hook call should be performed from address that must be the admin contract. SetBeforeSendHook *SetBeforeSendHook `json:"set_before_send_hook,omitempty"` + /// Force transferring of a specific denom is only allowed for the creator of the denom registered during CreateDenom. + ForceTransfer *ForceTransfer `json:"force_transfer,omitempty"` + /// Setting of metadata for a specific denom is only allowed for the admin of the denom. + /// It allows the overwriting of the denom metadata in the bank module. + SetDenomMetadata *SetDenomMetadata `json:"set_denom_metadata,omitempty"` // Cron types AddSchedule *AddSchedule `json:"add_schedule,omitempty"` @@ -185,11 +191,26 @@ type BurnTokens struct { BurnFromAddress string `json:"burn_from_address"` } +// SetBeforeSendHook Allowing to assign a CosmWasm contract to call with a BeforeSend hook for a specific denom is only +// allowed for the creator of the denom registered during CreateDenom. type SetBeforeSendHook struct { Denom string `json:"denom"` ContractAddr string `json:"contract_addr"` } +// SetDenomMetadata is sets the denom's bank metadata +type SetDenomMetadata struct { + banktypes.Metadata +} + +// ForceTransfer forces transferring of a specific denom is only allowed for the creator of the denom registered during CreateDenom. +type ForceTransfer struct { + Denom string `json:"denom"` + Amount math.Int `json:"amount"` + TransferFromAddress string `json:"transfer_from_address"` + TransferToAddress string `json:"transfer_to_address"` +} + // AddSchedule adds new schedule to the cron module type AddSchedule struct { Name string `json:"name"` diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index 81827739d..c25b91b6c 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -121,6 +121,7 @@ func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre if contractMsg.SubmitAdminProposal != nil { return m.submitAdminProposal(ctx, contractAddr, &contractMsg.SubmitAdminProposal.AdminProposal) } + if contractMsg.CreateDenom != nil { return m.createDenom(ctx, contractAddr, contractMsg.CreateDenom) } @@ -136,6 +137,13 @@ func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre if contractMsg.BurnTokens != nil { return m.burnTokens(ctx, contractAddr, contractMsg.BurnTokens) } + if contractMsg.ForceTransfer != nil { + return m.forceTransfer(ctx, contractAddr, contractMsg.ForceTransfer) + } + if contractMsg.SetDenomMetadata != nil { + return m.setDenomMetadata(ctx, contractAddr, contractMsg.SetDenomMetadata) + } + if contractMsg.AddSchedule != nil { return m.addSchedule(ctx, contractAddr, contractMsg.AddSchedule) } @@ -467,7 +475,7 @@ func (m *CustomMessenger) performSubmitAdminProposal(ctx sdk.Context, contractAd // createDenom creates a new token denom func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindings.CreateDenom) ([]sdk.Event, [][]byte, error) { - err := PerformCreateDenom(m.TokenFactory, m.Bank, ctx, contractAddr, createDenom) + err := PerformCreateDenom(m.TokenFactory, ctx, contractAddr, createDenom) if err != nil { return nil, nil, errors.Wrap(err, "perform create denom") } @@ -475,7 +483,7 @@ func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddre } // PerformCreateDenom is used with createDenom to create a token denom; validates the msgCreateDenom. -func PerformCreateDenom(f *tokenfactorykeeper.Keeper, _ *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindings.CreateDenom) error { +func PerformCreateDenom(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindings.CreateDenom) error { msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) msgCreateDenom := tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom) @@ -495,6 +503,66 @@ func PerformCreateDenom(f *tokenfactorykeeper.Keeper, _ *bankkeeper.BaseKeeper, return nil } +// createDenom forces a transfer of a tokenFactory token +func (m *CustomMessenger) forceTransfer(ctx sdk.Context, contractAddr sdk.AccAddress, forceTransfer *bindings.ForceTransfer) ([]sdk.Event, [][]byte, error) { + err := PerformForceTransfer(m.TokenFactory, ctx, contractAddr, forceTransfer) + if err != nil { + return nil, nil, errors.Wrap(err, "perform force transfer") + } + return nil, nil, nil +} + +// PerformForceTransfer is used with forceTransfer to force a tokenfactory token transfer; validates the msgForceTransfer. +func PerformForceTransfer(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, forceTransfer *bindings.ForceTransfer) error { + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + + msgForceTransfer := tokenfactorytypes.NewMsgForceTransfer(contractAddr.String(), sdk.NewInt64Coin(forceTransfer.Denom, forceTransfer.Amount.Int64()), forceTransfer.TransferFromAddress, forceTransfer.TransferToAddress) + + if err := msgForceTransfer.ValidateBasic(); err != nil { + return errors.Wrap(err, "failed validating MsgForceTransfer") + } + + // Force Transfer + _, err := msgServer.ForceTransfer( + sdk.WrapSDKContext(ctx), + msgForceTransfer, + ) + if err != nil { + return errors.Wrap(err, "forcing transfer") + } + return nil +} + +// setDenomMetadata sets a metadata for a tokenfactory denom +func (m *CustomMessenger) setDenomMetadata(ctx sdk.Context, contractAddr sdk.AccAddress, setDenomMetadata *bindings.SetDenomMetadata) ([]sdk.Event, [][]byte, error) { + err := PerformSetDenomMetadata(m.TokenFactory, ctx, contractAddr, setDenomMetadata) + if err != nil { + return nil, nil, errors.Wrap(err, "perform set denom metadata") + } + return nil, nil, nil +} + +// PerformSetDenomMetadata is used with setDenomMetadata to set a metadata for a tokenfactory denom; validates the msgSetDenomMetadata. +func PerformSetDenomMetadata(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, setDenomMetadata *bindings.SetDenomMetadata) error { + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + + msgSetDenomMetadata := tokenfactorytypes.NewMsgSetDenomMetadata(contractAddr.String(), setDenomMetadata.Metadata) + + if err := msgSetDenomMetadata.ValidateBasic(); err != nil { + return errors.Wrap(err, "failed validating MsgSetDenomMetadata") + } + + // Set denom metadata + _, err := msgServer.SetDenomMetadata( + sdk.WrapSDKContext(ctx), + msgSetDenomMetadata, + ) + if err != nil { + return errors.Wrap(err, "setting denom metadata") + } + return nil +} + // mintTokens mints tokens of a specified denom to an address. func (m *CustomMessenger) mintTokens(ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindings.MintTokens) ([]sdk.Event, [][]byte, error) { err := PerformMint(m.TokenFactory, m.Bank, ctx, contractAddr, mint) @@ -896,7 +964,6 @@ func (m *CustomMessenger) resubmitFailure(ctx sdk.Context, contractAddr sdk.AccA } err = m.ContractmanagerKeeper.ResubmitFailure(ctx, contractAddr, failure) - if err != nil { ctx.Logger().Error("failed to resubmitFailure", "from_address", contractAddr.String(), diff --git a/wasmbinding/test/custom_message_test.go b/wasmbinding/test/custom_message_test.go index 95956bb63..980ea49f8 100644 --- a/wasmbinding/test/custom_message_test.go +++ b/wasmbinding/test/custom_message_test.go @@ -5,6 +5,10 @@ import ( "fmt" "testing" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + admintypes "github.com/cosmos/admin-module/x/adminmodule/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + keeper2 "github.com/neutron-org/neutron/v2/x/contractmanager/keeper" feeburnertypes "github.com/neutron-org/neutron/v2/x/feeburner/types" @@ -15,7 +19,6 @@ import ( ictxtypes "github.com/neutron-org/neutron/v2/x/interchaintxs/types" adminkeeper "github.com/cosmos/admin-module/x/adminmodule/keeper" - admintypes "github.com/cosmos/admin-module/x/adminmodule/types" "github.com/neutron-org/neutron/v2/app/params" @@ -47,6 +50,7 @@ type CustomMessengerTestSuite struct { messenger *wasmbinding.CustomMessenger contractOwner sdk.AccAddress contractAddress sdk.AccAddress + contractKeeper wasmtypes.ContractOpsKeeper } func (suite *CustomMessengerTestSuite) SetupTest() { @@ -65,20 +69,21 @@ func (suite *CustomMessengerTestSuite) SetupTest() { suite.messenger.ContractmanagerKeeper = &suite.neutron.ContractManagerKeeper suite.contractOwner = keeper.RandomAccountAddress(suite.T()) + suite.contractKeeper = keeper.NewDefaultPermissionKeeper(&suite.neutron.WasmKeeper) + err := suite.messenger.TokenFactory.SetParams(suite.ctx, tokenfactorytypes.NewParams( sdk.NewCoins(sdk.NewInt64Coin(params.DefaultDenom, 10_000_000)), 0, FeeCollectorAddress, )) suite.Require().NoError(err) -} -func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccount() { - // Store code and instantiate reflect contract codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) suite.Require().NotEmpty(suite.contractAddress) +} +func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccount() { err := suite.neutron.FeeBurnerKeeper.SetParams(suite.ctx, feeburnertypes.Params{ NeutronDenom: "untrn", TreasuryAddress: "neutron13jrwrtsyjjuynlug65r76r2zvfw5xjcq6532h2", @@ -86,14 +91,13 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccount() { suite.Require().NoError(err) // Craft RegisterInterchainAccount message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ RegisterInterchainAccount: &bindings.RegisterInterchainAccount{ ConnectionId: suite.Path.EndpointA.ConnectionID, InterchainAccountId: testutil.TestInterchainID, RegisterFee: sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1_000_000))), }, - }) - suite.NoError(err) + } bankKeeper := suite.neutron.BankKeeper senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() @@ -101,20 +105,11 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccount() { suite.NoError(err) // Dispatch RegisterInterchainAccount message - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + _, err = suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) - suite.Equal([][]byte{[]byte(`{}`)}, data) } func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccountLongID() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - // Craft RegisterInterchainAccount message msg, err := json.Marshal(bindings.NeutronMsg{ RegisterInterchainAccount: &bindings.RegisterInterchainAccount{ @@ -125,7 +120,7 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccountLongID() { }) suite.NoError(err) - // Dispatch RegisterInterchainAccount message + // Dispatch RegisterInterchainAccount message via DispatchHandler cause we want to catch an error from SDK directly, not from a contract _, _, err = suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ Custom: msg, }) @@ -134,11 +129,6 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainAccountLongID() { } func (suite *CustomMessengerTestSuite) TestRegisterInterchainQuery() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - err := testutil.SetupICAPath(suite.Path, suite.contractAddress.String()) suite.Require().NoError(err) @@ -150,7 +140,7 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainQuery() { suite.NoError(err) // Craft RegisterInterchainQuery message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ RegisterInterchainQuery: &bindings.RegisterInterchainQuery{ QueryType: string(icqtypes.InterchainQueryTypeKV), Keys: []*icqtypes.KVKey{ @@ -160,23 +150,33 @@ func (suite *CustomMessengerTestSuite) TestRegisterInterchainQuery() { ConnectionId: suite.Path.EndpointA.ConnectionID, UpdatePeriod: 20, }, - }) - suite.NoError(err) + } // Dispatch RegisterInterchainQuery message - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + _, err = suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) - suite.Equal([][]byte{[]byte(`{"id":1}`)}, data) + suite.Equal(uint64(1), suite.neutron.InterchainQueriesKeeper.GetLastRegisteredQueryKey(suite.ctx)) } func (suite *CustomMessengerTestSuite) TestCreateDenomMsg() { - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) + senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() + coinsAmnt := sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(int64(10_000_000)))) + bankKeeper := suite.neutron.BankKeeper + err := bankKeeper.SendCoins(suite.ctx, senderAddress, suite.contractAddress, coinsAmnt) + suite.NoError(err) + + fullMsg := bindings.NeutronMsg{ + CreateDenom: &bindings.CreateDenom{ + Subdenom: "SUN", + }, + } + + data, err := suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) + suite.Empty(data) +} +func (suite *CustomMessengerTestSuite) TestSetDenomMetadataMsg() { senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() coinsAmnt := sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(int64(10_000_000)))) bankKeeper := suite.neutron.BankKeeper @@ -188,10 +188,59 @@ func (suite *CustomMessengerTestSuite) TestCreateDenomMsg() { Subdenom: "SUN", }, } + data, err := suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.Empty(data) + suite.NoError(err) + + sunDenom := fmt.Sprintf("factory/%s/%s", suite.contractAddress.String(), fullMsg.CreateDenom.Subdenom) + metadata := banktypes.Metadata{ + Description: "very nice description", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: sunDenom, + Exponent: 0, + Aliases: []string{"sun"}, + }, + }, + Base: sunDenom, + Display: sunDenom, + Name: "noname", + Symbol: sunDenom, + URI: "yuri", + URIHash: "sdjalkfjsdklfj", + } + + // this is the metadata variable but just in JSON representation, cause we can't marshal into json because of omitempty tags + metaMsgBz := ` +{ + "set_denom_metadata": { + "description": "very nice description", + "denom_units": [ + { + "denom": "factory/neutron14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s5c2epq/SUN", + "exponent": 0, + "aliases": [ + "sun" + ] + } + ], + "base": "factory/neutron14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s5c2epq/SUN", + "display": "factory/neutron14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s5c2epq/SUN", + "name": "noname", + "symbol": "factory/neutron14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s5c2epq/SUN", + "uri": "yuri", + "uri_hash": "sdjalkfjsdklfj" + } +} + ` - data, _ := suite.executeCustomMsg(suite.contractAddress, fullMsg) + data, err = suite.executeCustomMsg(suite.contractAddress, json.RawMessage(metaMsgBz)) + suite.Empty(data) + suite.NoError(err) - suite.Equal([][]byte(nil), data) + metaFromBank, ok := bankKeeper.GetDenomMetaData(suite.ctx, sunDenom) + suite.Require().True(ok) + suite.Equal(metadata, metaFromBank) } func (suite *CustomMessengerTestSuite) TestMintMsg() { @@ -201,10 +250,6 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { lucky = keeper.RandomAccountAddress(suite.T()) // We don't care what this address is ) - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() coinsAmnt := sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(int64(20_000_000)))) bankKeeper := suite.neutron.BankKeeper @@ -222,7 +267,8 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { }, } - suite.executeCustomMsg(suite.contractAddress, fullMsg) + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) sunDenom := fmt.Sprintf("factory/%s/%s", suite.contractAddress.String(), fullMsg.CreateDenom.Subdenom) @@ -237,7 +283,8 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { }, } - suite.executeCustomMsg(suite.contractAddress, fullMsg) + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) balances = neutron.BankKeeper.GetAllBalances(ctx, lucky) require.Len(suite.T(), balances, 1) @@ -247,7 +294,8 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { require.Equal(suite.T(), sunDenom, coin.Denom) // mint the same denom again - suite.executeCustomMsg(suite.contractAddress, fullMsg) + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) balances = neutron.BankKeeper.GetAllBalances(ctx, lucky) require.Len(suite.T(), balances, 1) @@ -263,7 +311,8 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { Subdenom: "MOON", }, } - suite.executeCustomMsg(suite.contractAddress, fullMsg) + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) moonDenom := fmt.Sprintf("factory/%s/%s", suite.contractAddress.String(), fullMsg.CreateDenom.Subdenom) @@ -276,7 +325,8 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { }, } - suite.executeCustomMsg(suite.contractAddress, fullMsg) + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) balances = neutron.BankKeeper.GetAllBalances(ctx, lucky) require.Len(suite.T(), balances, 2) @@ -286,50 +336,114 @@ func (suite *CustomMessengerTestSuite) TestMintMsg() { require.Equal(suite.T(), moonDenom, coin.Denom) } +func (suite *CustomMessengerTestSuite) TestForceTransferMsg() { + var ( + neutron = suite.GetNeutronZoneApp(suite.ChainA) + ctx = suite.ChainA.GetContext() + lucky = keeper.RandomAccountAddress(suite.T()) // We don't care what this address is + forceReceiver = keeper.RandomAccountAddress(suite.T()) // We don't care what this address is + ) + + senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() + coinsAmnt := sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(int64(20_000_000)))) + bankKeeper := suite.neutron.BankKeeper + err := bankKeeper.SendCoins(suite.ctx, senderAddress, suite.contractAddress, coinsAmnt) + suite.NoError(err) + + // lucky was broke + balances := neutron.BankKeeper.GetAllBalances(suite.ctx, lucky) + require.Empty(suite.T(), balances) + + // Create denom for minting + fullMsg := bindings.NeutronMsg{ + CreateDenom: &bindings.CreateDenom{ + Subdenom: "SUN", + }, + } + + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) + + sunDenom := fmt.Sprintf("factory/%s/%s", suite.contractAddress.String(), fullMsg.CreateDenom.Subdenom) + + amount, ok := sdk.NewIntFromString("808010808") + require.True(suite.T(), ok) + + fullMsg = bindings.NeutronMsg{ + MintTokens: &bindings.MintTokens{ + Denom: sunDenom, + Amount: amount, + MintToAddress: lucky.String(), + }, + } + + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) + + balances = neutron.BankKeeper.GetAllBalances(ctx, lucky) + require.Len(suite.T(), balances, 1) + coin := balances[0] + require.Equal(suite.T(), amount, coin.Amount) + require.Contains(suite.T(), coin.Denom, "factory/") + require.Equal(suite.T(), sunDenom, coin.Denom) + + // now perform a force transfer to transfer tokens from a lucky address to a forceReceiver + fullMsg = bindings.NeutronMsg{ + ForceTransfer: &bindings.ForceTransfer{ + Denom: sunDenom, + Amount: amount, + TransferFromAddress: lucky.String(), + TransferToAddress: forceReceiver.String(), + }, + } + + _, err = suite.executeNeutronMsg(suite.contractAddress, fullMsg) + suite.NoError(err) + + balancesLucky := neutron.BankKeeper.GetAllBalances(ctx, lucky) + require.Len(suite.T(), balancesLucky, 0) + balancesReceiver := neutron.BankKeeper.GetAllBalances(ctx, forceReceiver) + require.Len(suite.T(), balancesReceiver, 1) + + coinReceiver := balancesReceiver[0] + require.Equal(suite.T(), amount, coinReceiver.Amount) + require.Contains(suite.T(), coinReceiver.Denom, "factory/") + require.Equal(suite.T(), sunDenom, coinReceiver.Denom) +} + func (suite *CustomMessengerTestSuite) TestUpdateInterchainQuery() { // reuse register interchain query test to get query registered suite.TestRegisterInterchainQuery() // Craft UpdateInterchainQuery message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ UpdateInterchainQuery: &bindings.UpdateInterchainQuery{ QueryId: 1, NewKeys: nil, NewUpdatePeriod: 111, }, - }) - suite.NoError(err) + } // Dispatch UpdateInterchainQuery message - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + _, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) - suite.Equal([][]byte{[]byte(`{}`)}, data) } func (suite *CustomMessengerTestSuite) TestUpdateInterchainQueryFailed() { // Craft UpdateInterchainQuery message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ UpdateInterchainQuery: &bindings.UpdateInterchainQuery{ QueryId: 1, NewKeys: nil, NewUpdatePeriod: 1, }, - }) - suite.NoError(err) + } // Dispatch UpdateInterchainQuery message - owner, err := sdk.AccAddressFromBech32(testutil.TestOwnerAddress) - suite.NoError(err) - events, data, err := suite.messenger.DispatchMsg(suite.ctx, owner, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) expectedErrMsg := "failed to update interchain query: failed to update interchain query: failed to get query by query id: there is no query with id: 1" suite.Require().ErrorContains(err, expectedErrMsg) - suite.Nil(events) - suite.Nil(data) + suite.Empty(data) } func (suite *CustomMessengerTestSuite) TestRemoveInterchainQuery() { @@ -337,50 +451,33 @@ func (suite *CustomMessengerTestSuite) TestRemoveInterchainQuery() { suite.TestRegisterInterchainQuery() // Craft RemoveInterchainQuery message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ RemoveInterchainQuery: &bindings.RemoveInterchainQuery{ QueryId: 1, }, - }) - suite.NoError(err) + } // Dispatch RemoveInterchainQuery message + _, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) - suite.NoError(err) - suite.Nil(events) - suite.Equal([][]byte{[]byte(`{}`)}, data) } func (suite *CustomMessengerTestSuite) TestRemoveInterchainQueryFailed() { // Craft RemoveInterchainQuery message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ RemoveInterchainQuery: &bindings.RemoveInterchainQuery{ QueryId: 1, }, - }) - suite.NoError(err) + } // Dispatch RemoveInterchainQuery message - owner, err := sdk.AccAddressFromBech32(testutil.TestOwnerAddress) - suite.NoError(err) - events, data, err := suite.messenger.DispatchMsg(suite.ctx, owner, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) expectedErrMsg := "failed to remove interchain query: failed to remove interchain query: failed to get query by query id: there is no query with id: 1" suite.Require().ErrorContains(err, expectedErrMsg) - suite.Nil(events) - suite.Nil(data) + suite.Empty(data) } func (suite *CustomMessengerTestSuite) TestSubmitTx() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - senderAddress := suite.ChainA.SenderAccounts[0].SenderAccount.GetAddress() coinsAmnt := sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(int64(10_000_000)))) bankKeeper := suite.neutron.BankKeeper @@ -390,40 +487,26 @@ func (suite *CustomMessengerTestSuite) TestSubmitTx() { err = testutil.SetupICAPath(suite.Path, suite.contractAddress.String()) suite.Require().NoError(err) - events, data, err := suite.messenger.DispatchMsg( - suite.ctx, + data, err := suite.executeNeutronMsg( suite.contractAddress, - suite.Path.EndpointA.ChannelConfig.PortID, - types.CosmosMsg{ - Custom: suite.craftMarshaledMsgSubmitTxWithNumMsgs(1), - }, + suite.craftMarshaledMsgSubmitTxWithNumMsgs(1), ) suite.NoError(err) var response bindings.SubmitTxResponse - err = json.Unmarshal(data[0], &response) + err = json.Unmarshal(data, &response) suite.NoError(err) - suite.Nil(events) suite.Equal(uint64(1), response.SequenceId) suite.Equal("channel-2", response.Channel) } func (suite *CustomMessengerTestSuite) TestSubmitTxTooMuchTxs() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - err := testutil.SetupICAPath(suite.Path, suite.contractAddress.String()) suite.Require().NoError(err) - _, _, err = suite.messenger.DispatchMsg( - suite.ctx, + _, err = suite.executeNeutronMsg( suite.contractAddress, - suite.Path.EndpointA.ChannelConfig.PortID, - types.CosmosMsg{ - Custom: suite.craftMarshaledMsgSubmitTxWithNumMsgs(20), - }, + suite.craftMarshaledMsgSubmitTxWithNumMsgs(20), ) suite.ErrorContains(err, "MsgSubmitTx contains more messages than allowed") } @@ -432,6 +515,10 @@ func (suite *CustomMessengerTestSuite) TestSoftwareUpgradeProposal() { // Set admin so that we can execute this proposal without permission error suite.neutron.AdminmoduleKeeper.SetAdmin(suite.ctx, suite.contractAddress.String()) + codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") + anotherContract := suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) + suite.Require().NotEqual(anotherContract, suite.contractAddress) + executeMsg := fmt.Sprintf(` { "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", @@ -444,7 +531,7 @@ func (suite *CustomMessengerTestSuite) TestSoftwareUpgradeProposal() { } `, suite.neutron.BankKeeper.GetAuthority()) // Craft SubmitAdminProposal message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ SubmitAdminProposal: &bindings.SubmitAdminProposal{ AdminProposal: bindings.AdminProposal{ ProposalExecuteMessage: &bindings.ProposalExecuteMessage{ @@ -452,28 +539,20 @@ func (suite *CustomMessengerTestSuite) TestSoftwareUpgradeProposal() { }, }, }, - }) - suite.NoError(err) + } // Dispatch SubmitAdminProposal message - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) expected, err := json.Marshal(&admintypes.MsgSubmitProposalResponse{ ProposalId: 1, }) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) // Test with other proposer that is not admin should return failure - otherAddress, err := sdk.AccAddressFromBech32("neutron13jrwrtsyjjuynlug65r76r2zvfw5xjcq6532h2") - suite.NoError(err) - _, _, err = suite.messenger.DispatchMsg(suite.ctx, otherAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + _, err = suite.executeNeutronMsg(anotherContract, msg) suite.Error(err) // Check CancelSubmitAdminProposal @@ -485,24 +564,21 @@ func (suite *CustomMessengerTestSuite) TestSoftwareUpgradeProposal() { } `, suite.neutron.BankKeeper.GetAuthority()) // Craft CancelSubmitAdminProposal message - msg, err = json.Marshal(bindings.NeutronMsg{ + msg = bindings.NeutronMsg{ SubmitAdminProposal: &bindings.SubmitAdminProposal{ AdminProposal: bindings.AdminProposal{ProposalExecuteMessage: &bindings.ProposalExecuteMessage{Message: executeMsg}}, }, - }) - suite.NoError(err) + } // Dispatch SubmitAdminProposal message - events, data, err = suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err = suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) + expected, err = json.Marshal(&admintypes.MsgSubmitProposalResponse{ ProposalId: 2, }) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) } func (suite *CustomMessengerTestSuite) TestTooMuchProposals() { @@ -546,11 +622,6 @@ func (suite *CustomMessengerTestSuite) TestTooMuchProposals() { } func (suite *CustomMessengerTestSuite) TestNoProposals() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - err := testutil.SetupICAPath(suite.Path, suite.contractAddress.String()) suite.Require().NoError(err) @@ -571,16 +642,11 @@ func (suite *CustomMessengerTestSuite) TestNoProposals() { } func (suite *CustomMessengerTestSuite) TestAddRemoveSchedule() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - // Set admin so that we can execute this proposal without permission error suite.neutron.AdminmoduleKeeper.SetAdmin(suite.ctx, suite.contractAddress.String()) // Craft AddSchedule message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ AddSchedule: &bindings.AddSchedule{ Name: "schedule1", Period: 5, @@ -591,44 +657,33 @@ func (suite *CustomMessengerTestSuite) TestAddRemoveSchedule() { }, }, }, - }) - suite.NoError(err) + } // Dispatch AddSchedule message - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) + expected, err := json.Marshal(&bindings.AddScheduleResponse{}) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) // Craft RemoveSchedule message - msg, err = json.Marshal(bindings.NeutronMsg{ + msg = bindings.NeutronMsg{ RemoveSchedule: &bindings.RemoveSchedule{ Name: "schedule1", }, - }) - suite.NoError(err) + } // Dispatch AddSchedule message - events, data, err = suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err = suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) + expected, err = json.Marshal(&bindings.RemoveScheduleResponse{}) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) } func (suite *CustomMessengerTestSuite) TestResubmitFailureAck() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - // Add failure packet := ibcchanneltypes.Packet{} ack := ibcchanneltypes.Acknowledgement{ @@ -640,30 +695,22 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureAck() { suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, suite.contractAddress.String(), payload, "test error") // Craft message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ ResubmitFailure: &bindings.ResubmitFailure{ FailureId: failureID, }, - }) - suite.NoError(err) + } // Dispatch - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) + expected, err := json.Marshal(&bindings.ResubmitFailureResponse{}) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) } func (suite *CustomMessengerTestSuite) TestResubmitFailureTimeout() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - // Add failure packet := ibcchanneltypes.Packet{} payload, err := keeper2.PrepareSudoCallbackMessage(packet, nil) @@ -672,30 +719,22 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureTimeout() { suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, suite.contractAddress.String(), payload, "test error") // Craft message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ ResubmitFailure: &bindings.ResubmitFailure{ FailureId: failureID, }, - }) - suite.NoError(err) + } // Dispatch - events, data, err := suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + data, err := suite.executeNeutronMsg(suite.contractAddress, msg) suite.NoError(err) - suite.Nil(events) + expected, err := json.Marshal(&bindings.ResubmitFailureResponse{FailureId: failureID}) suite.NoError(err) - suite.Equal([][]uint8{expected}, data) + suite.Equal(expected, data) } func (suite *CustomMessengerTestSuite) TestResubmitFailureFromDifferentContract() { - // Store code and instantiate reflect contract - codeID := suite.StoreTestCode(suite.ctx, suite.contractOwner, "../testdata/reflect.wasm") - suite.contractAddress = suite.InstantiateTestContract(suite.ctx, suite.contractOwner, codeID) - suite.Require().NotEmpty(suite.contractAddress) - // Add failure packet := ibcchanneltypes.Packet{} ack := ibcchanneltypes.Acknowledgement{ @@ -707,34 +746,48 @@ func (suite *CustomMessengerTestSuite) TestResubmitFailureFromDifferentContract( suite.messenger.ContractmanagerKeeper.AddContractFailure(suite.ctx, testutil.TestOwnerAddress, payload, "test error") // Craft message - msg, err := json.Marshal(bindings.NeutronMsg{ + msg := bindings.NeutronMsg{ ResubmitFailure: &bindings.ResubmitFailure{ FailureId: failureID, }, - }) - suite.NoError(err) + } // Dispatch - _, _, err = suite.messenger.DispatchMsg(suite.ctx, suite.contractAddress, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + _, err = suite.executeNeutronMsg(suite.contractAddress, msg) suite.ErrorContains(err, "no failure found to resubmit: not found") } -func (suite *CustomMessengerTestSuite) executeCustomMsg(owner sdk.AccAddress, fullMsg bindings.NeutronMsg) (result [][]byte, msg []byte) { - msg, err := json.Marshal(fullMsg) - suite.NoError(err) +func (suite *CustomMessengerTestSuite) executeCustomMsg(contractAddress sdk.AccAddress, fullMsg json.RawMessage) (data []byte, err error) { + customMsg := types.CosmosMsg{ + Custom: fullMsg, + } - events, result, err := suite.messenger.DispatchMsg(suite.ctx, owner, suite.Path.EndpointA.ChannelConfig.PortID, types.CosmosMsg{ - Custom: msg, - }) + type ExecuteMsg struct { + ReflectMsg struct { + Msgs []types.CosmosMsg `json:"msgs"` + } `json:"reflect_msg"` + } + + execMsg := ExecuteMsg{ReflectMsg: struct { + Msgs []types.CosmosMsg `json:"msgs"` + }(struct{ Msgs []types.CosmosMsg }{Msgs: []types.CosmosMsg{customMsg}})} + + msg, err := json.Marshal(execMsg) suite.NoError(err) - suite.Nil(events) + + data, err = suite.contractKeeper.Execute(suite.ctx, contractAddress, suite.contractOwner, msg, nil) return } -func (suite *CustomMessengerTestSuite) craftMarshaledMsgSubmitTxWithNumMsgs(numMsgs int) (result []byte) { +func (suite *CustomMessengerTestSuite) executeNeutronMsg(contractAddress sdk.AccAddress, fullMsg bindings.NeutronMsg) (data []byte, err error) { + fullMsgBz, err := json.Marshal(fullMsg) + suite.NoError(err) + + return suite.executeCustomMsg(contractAddress, fullMsgBz) +} + +func (suite *CustomMessengerTestSuite) craftMarshaledMsgSubmitTxWithNumMsgs(numMsgs int) bindings.NeutronMsg { msg := bindings.ProtobufAny{ TypeURL: "/cosmos.staking.v1beta1.MsgDelegate", Value: []byte{26, 10, 10, 5, 115, 116, 97, 107, 101, 18, 1, 48}, @@ -743,10 +796,8 @@ func (suite *CustomMessengerTestSuite) craftMarshaledMsgSubmitTxWithNumMsgs(numM for i := 0; i < numMsgs; i++ { msgs = append(msgs, msg) } - result, err := json.Marshal(struct { - SubmitTx bindings.SubmitTx `json:"submit_tx"` - }{ - SubmitTx: bindings.SubmitTx{ + result := bindings.NeutronMsg{ + SubmitTx: &bindings.SubmitTx{ ConnectionId: suite.Path.EndpointA.ConnectionID, InterchainAccountId: testutil.TestInterchainID, Msgs: msgs, @@ -758,9 +809,8 @@ func (suite *CustomMessengerTestSuite) craftMarshaledMsgSubmitTxWithNumMsgs(numM TimeoutFee: sdk.NewCoins(sdk.NewCoin(params.DefaultDenom, sdk.NewInt(1000))), }, }, - }) - suite.NoError(err) - return + } + return result } func TestMessengerTestSuite(t *testing.T) { diff --git a/wasmbinding/testdata/reflect.wasm b/wasmbinding/testdata/reflect.wasm index ab380cbe2..174232f4b 100644 Binary files a/wasmbinding/testdata/reflect.wasm and b/wasmbinding/testdata/reflect.wasm differ diff --git a/x/tokenfactory/client/cli/tx.go b/x/tokenfactory/client/cli/tx.go index 7fbf57933..a84b4e000 100644 --- a/x/tokenfactory/client/cli/tx.go +++ b/x/tokenfactory/client/cli/tx.go @@ -1,8 +1,11 @@ package cli import ( + "encoding/json" "fmt" + "os" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -28,9 +31,10 @@ func GetTxCmd() *cobra.Command { NewCreateDenomCmd(), NewMintCmd(), NewBurnCmd(), - // NewForceTransferCmd(), + NewForceTransferCmd(), NewChangeAdminCmd(), NewSetBeforeSendHook(), + NewSetDenomMetadataCmd(), ) return cmd @@ -136,39 +140,82 @@ func NewBurnCmd() *cobra.Command { return cmd } -// // NewForceTransferCmd broadcast MsgForceTransfer -// func NewForceTransferCmd() *cobra.Command { -// cmd := &cobra.Command{ -// Use: "force-transfer [amount] [transfer-from-address] [transfer-to-address] [flags]", -// Short: "Force transfer tokens from one address to another address. Must have admin authority to do so.", -// Args: cobra.ExactArgs(3), -// RunE: func(cmd *cobra.Command, args []string) error { -// clientCtx, err := client.GetClientTxContext(cmd) -// if err != nil { -// return err -// } - -// txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) - -// amount, err := sdk.ParseCoinNormalized(args[0]) -// if err != nil { -// return err -// } - -// msg := types.NewMsgForceTransfer( -// clientCtx.GetFromAddress().String(), -// amount, -// args[1], -// args[2], -// ) - -// return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) -// }, -// } - -// flags.AddTxFlagsToCmd(cmd) -// return cmd -// } +// NewForceTransferCmd broadcast MsgForceTransfer +func NewForceTransferCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "force-transfer [amount] [transfer-from-address] [transfer-to-address] [flags]", + Short: "Force transfer tokens from one address to another address. Must have admin authority to do so.", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + amount, err := sdk.ParseCoinNormalized(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgForceTransfer( + clientCtx.GetFromAddress().String(), + amount, + args[1], + args[2], + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewSetDenomMetadataCmd broadcast MsgSetDenomMetadata msg +func NewSetDenomMetadataCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "set-denom-metadata [metadata-file] [flags]", + Short: "Sets a bank metadata for a token denom. Must have admin authority to do so.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + metadataBytes, err := os.ReadFile(args[1]) + if err != nil { + return err + } + + var metadata banktypes.Metadata + if err = json.Unmarshal(metadataBytes, &metadata); err != nil { + return err + } + + msg := types.NewMsgSetDenomMetadata( + clientCtx.GetFromAddress().String(), + metadata, + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} // NewChangeAdminCmd broadcast MsgChangeAdmin func NewChangeAdminCmd() *cobra.Command { diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go index fceee2eae..52a68cbef 100644 --- a/x/tokenfactory/keeper/admins_test.go +++ b/x/tokenfactory/keeper/admins_test.go @@ -112,7 +112,7 @@ func (suite *KeeperTestSuite) TestMintDenom() { } { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account - _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) + _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, tc.amount))) if tc.valid { addr0bal += 10 suite.Require().NoError(err) @@ -174,7 +174,7 @@ func (suite *KeeperTestSuite) TestBurnDenom() { } { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account - _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) + _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, tc.amount))) if tc.valid { addr0bal -= 10 suite.Require().NoError(err) @@ -187,6 +187,73 @@ func (suite *KeeperTestSuite) TestBurnDenom() { } } +func (suite *KeeperTestSuite) TestForceTransferDenom() { + addr0bal := int64(0) + addr1bal := int64(0) + suite.Setup() + + // Create a denom. + suite.CreateDefaultDenom(suite.ChainA.GetContext()) + + // mint 10 default token for testAcc[0] + _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + suite.NoError(err) + addr0bal += 10 + + for _, tc := range []struct { + desc string + amount int64 + transferDenom string + admin string + valid bool + }{ + { + desc: "denom does not exist", + amount: 10, + transferDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "force transfer is not by the admin", + amount: 10, + transferDenom: suite.defaultDenom, + admin: suite.TestAccs[1].String(), + valid: false, + }, + { + desc: "force transfer amount is bigger than minted amount", + amount: 1000, + transferDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "success case", + amount: 10, + transferDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: true, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // Test minting to admins own account + _, err := suite.msgServer.ForceTransfer(sdk.WrapSDKContext(suite.ChainA.GetContext()), types.NewMsgForceTransfer(tc.admin, sdk.NewInt64Coin(tc.transferDenom, tc.amount), suite.TestAccs[0].String(), suite.TestAccs[1].String())) + if tc.valid { + addr0bal -= 10 + addr1bal += 10 + + suite.Require().NoError(err) + suite.Require().True(suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[0], suite.defaultDenom)) + suite.Require().True(suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal, suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[1], suite.defaultDenom)) + } else { + suite.Require().Error(err) + suite.Require().True(suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, suite.GetNeutronZoneApp(suite.ChainA).BankKeeper.GetBalance(suite.ChainA.GetContext(), suite.TestAccs[0], suite.defaultDenom)) + } + }) + } +} + func (suite *KeeperTestSuite) TestChangeAdminDenom() { for _, tc := range []struct { desc string diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go index 1f39f08bb..f22746c33 100644 --- a/x/tokenfactory/keeper/createdenom.go +++ b/x/tokenfactory/keeper/createdenom.go @@ -102,7 +102,6 @@ func (k Keeper) chargeForCreateDenom(ctx sdk.Context, creatorAddr string) (err e accAddr, feeCollectorAddr, params.DenomCreationFee, ) - if err != nil { return errors.Wrap(err, "unable to send coins to fee collector") } diff --git a/x/tokenfactory/types/msgs.go b/x/tokenfactory/types/msgs.go index 4020c3cf2..77190c4ea 100644 --- a/x/tokenfactory/types/msgs.go +++ b/x/tokenfactory/types/msgs.go @@ -225,7 +225,7 @@ func (m MsgChangeAdmin) GetSigners() []sdk.AccAddress { var _ sdk.Msg = &MsgSetDenomMetadata{} -// NewMsgChangeAdmin creates a message to burn tokens +// NewMsgSetDenomMetadata creates a message to set a metadata for a denom func NewMsgSetDenomMetadata(sender string, metadata banktypes.Metadata) *MsgSetDenomMetadata { return &MsgSetDenomMetadata{ Sender: sender,