diff --git a/simapp/app.go b/simapp/app.go index 53e671a1242d..347e32a687c2 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -22,6 +22,8 @@ import ( distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/ibc" + ibctransfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" @@ -54,16 +56,18 @@ var ( params.AppModuleBasic{}, crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, + ibc.AppModuleBasic{}, ) // module account permissions maccPerms = map[string][]string{ - auth.FeeCollectorName: nil, - distr.ModuleName: nil, - mint.ModuleName: {supply.Minter}, - staking.BondedPoolName: {supply.Burner, supply.Staking}, - staking.NotBondedPoolName: {supply.Burner, supply.Staking}, - gov.ModuleName: {supply.Burner}, + auth.FeeCollectorName: nil, + distr.ModuleName: nil, + mint.ModuleName: {supply.Minter}, + staking.BondedPoolName: {supply.Burner, supply.Staking}, + staking.NotBondedPoolName: {supply.Burner, supply.Staking}, + gov.ModuleName: {supply.Burner}, + ibctransfer.GetModuleAccountName(): {supply.Minter, supply.Burner}, } ) @@ -101,6 +105,7 @@ type SimApp struct { GovKeeper gov.Keeper CrisisKeeper crisis.Keeper ParamsKeeper params.Keeper + IBCKeeper ibc.Keeper // the module manager mm *module.Manager @@ -123,7 +128,7 @@ func NewSimApp( keys := sdk.NewKVStoreKeys(bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, - gov.StoreKey, params.StoreKey) + gov.StoreKey, params.StoreKey, ibc.StoreKey) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) app := &SimApp{ @@ -172,6 +177,8 @@ func NewSimApp( staking.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), ) + app.IBCKeeper = ibc.NewKeeper(app.cdc, keys[ibc.StoreKey], ibc.DefaultCodespace, app.BankKeeper, app.SupplyKeeper) + // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. app.mm = module.NewManager( @@ -185,6 +192,7 @@ func NewSimApp( distr.NewAppModule(app.DistrKeeper, app.SupplyKeeper), slashing.NewAppModule(app.SlashingKeeper, app.StakingKeeper), staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.SupplyKeeper), + ibc.NewAppModule(app.IBCKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -200,7 +208,7 @@ func NewSimApp( auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, - genutil.ModuleName, + ibc.ModuleName, genutil.ModuleName, ) app.mm.RegisterInvariants(&app.CrisisKeeper) diff --git a/types/result.go b/types/result.go index 41a31b84ba3c..5821cab538bc 100644 --- a/types/result.go +++ b/types/result.go @@ -100,6 +100,15 @@ type TxResponse struct { Events StringEvents `json:"events,omitempty"` } +func (res TxResponse) IsOK() bool { + for _, lg := range res.Logs { + if !lg.Success { + return false + } + } + return true +} + // NewResponseResultTx returns a TxResponse given a ResultTx from tendermint func NewResponseResultTx(res *ctypes.ResultTx, tx Tx, timestamp string) TxResponse { if res == nil { diff --git a/x/auth/client/utils/tx.go b/x/auth/client/utils/tx.go index d2212a039ad4..ccba257d4ded 100644 --- a/x/auth/client/utils/tx.go +++ b/x/auth/client/utils/tx.go @@ -113,6 +113,45 @@ func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLICon return cliCtx.PrintOutput(res) } +// CompleteAndBroadcastTx implements a utility function that facilitates +// sending a series of messages in a signed transaction given a TxBuilder and a +// QueryContext. It ensures that the account exists, has a proper number and +// sequence set. In addition, it builds and signs a transaction non-interactively +// with the supplied messages. Finally, it broadcasts the signed transaction to a node. +func CompleteAndBroadcastTx(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, passphrase string) (sdk.TxResponse, error) { + var res sdk.TxResponse + txBldr, err := PrepareTxBuilder(txBldr, cliCtx) + if err != nil { + return res, err + } + + fromName := cliCtx.GetFromName() + + if txBldr.SimulateAndExecute() || cliCtx.Simulate { + txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) + if err != nil { + return res, err + } + + gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()} + _, _ = fmt.Fprintf(os.Stderr, "%s\n", gasEst.String()) + } + + // build and sign the transaction + txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs) + if err != nil { + return res, err + } + + // broadcast to a Tendermint node + res, err = cliCtx.BroadcastTx(txBytes) + if err != nil { + return res, err + } + + return res, err +} + // EnrichWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. func EnrichWithGas(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtypes.TxBuilder, error) { diff --git a/x/ibc/03-connection/client/cli/tx.go b/x/ibc/03-connection/client/cli/tx.go index 22ccf7777d31..cef560ccdd57 100644 --- a/x/ibc/03-connection/client/cli/tx.go +++ b/x/ibc/03-connection/client/cli/tx.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" @@ -349,6 +350,18 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } + // get passphrase for key from1 + passphrase1, err := keys.GetPassphrase(from1) + if err != nil { + return err + } + + // get passphrase for key from2 + passphrase2, err := keys.GetPassphrase(from2) + if err != nil { + return err + } + viper.Set(flags.FlagChainID, cid1) msgOpenInit := types.NewMsgConnectionOpenInit( connID1, clientID1, connID2, clientID2, @@ -359,11 +372,14 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgOpenInit}) - if err != nil { + fmt.Printf("%v <- %-14v", cid1, msgOpenInit.Type()) + res, err := utils.CompleteAndBroadcastTx(txBldr1, ctx1, []sdk.Msg{msgOpenInit}, passphrase1) + if err != nil || !res.IsOK() { return err } + fmt.Printf(" [OK] txid(%v) client(%v) conn(%v)\n", res.TxHash, clientID1, connID1) + // Another block has to be passed after msgOpenInit is committed // to retrieve the correct proofs // TODO: Modify this to actually check two blocks being processed, and @@ -383,11 +399,12 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}) - if err != nil { + fmt.Printf("%v <- %-14v", cid2, msgUpdateClient.Type()) + res, err = utils.CompleteAndBroadcastTx(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}, passphrase2) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] client(%v)\n", clientID1) + fmt.Printf(" [OK] txid(%v) client(%v)\n", res.TxHash, clientID1) // Fetch proofs from cid1 viper.Set(flags.FlagChainID, cid1) @@ -409,12 +426,14 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgOpenTry}) - if err != nil { + fmt.Printf("%v <- %-14v", cid2, msgOpenTry.Type()) + + res, err = utils.CompleteAndBroadcastTx(txBldr2, ctx2, []sdk.Msg{msgOpenTry}, passphrase2) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] client(%v) connection(%v)\n", clientID2, connID2) + fmt.Printf(" [OK] txid(%v) client(%v) connection(%v)\n", res.TxHash, clientID2, connID2) // Another block has to be passed after msgOpenInit is committed // to retrieve the correct proofs @@ -435,11 +454,11 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgUpdateClient}) - if err != nil { + res, err = utils.CompleteAndBroadcastTx(txBldr1, ctx1, []sdk.Msg{msgUpdateClient}, passphrase1) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] client(%v)\n", clientID2) + fmt.Printf(" [OK] txid(%v) client(%v)\n", res.TxHash, clientID2) // Fetch proofs from cid2 viper.Set(flags.FlagChainID, cid2) @@ -461,11 +480,13 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgOpenAck}) - if err != nil { + fmt.Printf("%v <- %-14v", cid1, msgOpenAck.Type()) + + res, err = utils.CompleteAndBroadcastTx(txBldr1, ctx1, []sdk.Msg{msgOpenAck}, passphrase1) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] connection(%v)\n", connID1) + fmt.Printf(" [OK] txid(%v) connection(%v)\n", res.TxHash, connID1) // Another block has to be passed after msgOpenInit is committed // to retrieve the correct proofs @@ -486,13 +507,14 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}) - if err != nil { + fmt.Printf("%v <- %-14v", cid2, msgUpdateClient.Type()) + + res, err = utils.CompleteAndBroadcastTx(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}, passphrase2) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] client(%v)\n", clientID1) + fmt.Printf(" [OK] txid(%v) client(%v)\n", res.TxHash, clientID1) - // Fetch proof from cid1 viper.Set(flags.FlagChainID, cid1) proofs, err = queryProofs(ctx1.WithHeight(header.Height-1), connID1, storeKey) if err != nil { @@ -507,11 +529,13 @@ func GetCmdHandshakeState(storeKey string, cdc *codec.Codec) *cobra.Command { return err } - err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgOpenConfirm}) - if err != nil { + fmt.Printf("%v <- %-14v", cid1, msgOpenConfirm.Type()) + + res, err = utils.CompleteAndBroadcastTx(txBldr2, ctx2, []sdk.Msg{msgOpenConfirm}, passphrase2) + if err != nil || !res.IsOK() { return err } - fmt.Printf(" [OK] connection(%v)\n", connID2) + fmt.Printf(" [OK] txid(%v) connection(%v)\n", res.TxHash, connID2) return nil }, diff --git a/x/ibc/20-transfer/alias.go b/x/ibc/20-transfer/alias.go new file mode 100644 index 000000000000..66028740581e --- /dev/null +++ b/x/ibc/20-transfer/alias.go @@ -0,0 +1,62 @@ +package transfer + +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types + +import ( + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" +) + +const ( + DefaultPacketTimeout = keeper.DefaultPacketTimeout + DefaultCodespace = types.DefaultCodespace + CodeInvalidAddress = types.CodeInvalidAddress + CodeErrSendPacket = types.CodeErrSendPacket + CodeInvalidPacketData = types.CodeInvalidPacketData + CodeInvalidChannelOrder = types.CodeInvalidChannelOrder + CodeInvalidPort = types.CodeInvalidPort + CodeInvalidVersion = types.CodeInvalidVersion + AttributeKeyReceiver = types.AttributeKeyReceiver + SubModuleName = types.SubModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + BoundPortID = types.BoundPortID +) + +var ( + // functions aliases + NewKeeper = keeper.NewKeeper + RegisterCodec = types.RegisterCodec + ErrInvalidAddress = types.ErrInvalidAddress + ErrSendPacket = types.ErrSendPacket + ErrInvalidPacketData = types.ErrInvalidPacketData + ErrInvalidChannelOrder = types.ErrInvalidChannelOrder + ErrInvalidPort = types.ErrInvalidPort + ErrInvalidVersion = types.ErrInvalidVersion + GetEscrowAddress = types.GetEscrowAddress + GetDenomPrefix = types.GetDenomPrefix + GetModuleAccountName = types.GetModuleAccountName + NewMsgTransfer = types.NewMsgTransfer + + // variable aliases + ModuleCdc = types.ModuleCdc + AttributeValueCategory = types.AttributeValueCategory +) + +type ( + Keeper = keeper.Keeper + BankKeeper = types.BankKeeper + ChannelKeeper = types.ChannelKeeper + ClientKeeper = types.ClientKeeper + ConnectionKeeper = types.ConnectionKeeper + SupplyKeeper = types.SupplyKeeper + MsgTransfer = types.MsgTransfer + MsgRecvPacket = types.MsgRecvPacket + PacketData = types.PacketData + PacketDataAlias = types.PacketDataAlias +) diff --git a/x/ibc/20-transfer/client/cli/query.go b/x/ibc/20-transfer/client/cli/query.go new file mode 100644 index 000000000000..1795e1919edb --- /dev/null +++ b/x/ibc/20-transfer/client/cli/query.go @@ -0,0 +1,68 @@ +package cli + +import ( + "encoding/binary" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/version" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + abci "github.com/tendermint/tendermint/abci/types" +) + +// GetTxCmd returns the transaction commands for IBC fungible token transfer +func GetQueryCmd(cdc *codec.Codec, storeKey string) *cobra.Command { + queryCmd := &cobra.Command{ + Use: "transfer", + Short: "IBC fungible token transfer query subcommands", + } + + queryCmd.AddCommand( + GetCmdQueryNextSequence(cdc, storeKey), + ) + + return queryCmd +} + +// GetCmdQueryNextSequence defines the command to query a next receive sequence +func GetCmdQueryNextSequence(cdc *codec.Codec, queryRoute string) *cobra.Command { + cmd := &cobra.Command{ + Use: "next-recv [port-id] [channel-id]", + Short: "Query a next receive sequence", + Long: strings.TrimSpace(fmt.Sprintf(`Query an IBC channel end + +Example: +$ %s query ibc channel next-recv [port-id] [channel-id] + `, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + portID := args[0] + channelID := args[1] + + req := abci.RequestQuery{ + Path: "store/ibc/key", + Data: channel.KeyNextSequenceRecv(portID, channelID), + Prove: true, + } + + res, err := cliCtx.QueryABCI(req) + if err != nil { + return err + } + + sequence := binary.BigEndian.Uint64(res.Value) + + return cliCtx.PrintOutput(sequence) + }, + } + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + + return cmd +} diff --git a/x/ibc/20-transfer/client/cli/tx.go b/x/ibc/20-transfer/client/cli/tx.go new file mode 100644 index 000000000000..28870567b55c --- /dev/null +++ b/x/ibc/20-transfer/client/cli/tx.go @@ -0,0 +1,150 @@ +package cli + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + clientutils "github.com/cosmos/cosmos-sdk/x/ibc/02-client/client/utils" + channelutils "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/client/utils" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// IBC transfer flags +var ( + FlagSource = "source" + FlagNode1 = "node1" + FlagNode2 = "node2" + FlagFrom1 = "from1" + FlagFrom2 = "from2" + FlagChainId2 = "chain-id2" + FlagSequence = "packet-sequence" + FlagTimeout = "timeout" +) + +// GetTxCmd returns the transaction commands for IBC fungible token transfer +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: "transfer", + Short: "IBC fungible token transfer transaction subcommands", + } + txCmd.AddCommand( + GetTransferTxCmd(cdc), + GetMsgRecvPacketCmd(cdc), + ) + + return txCmd +} + +// GetTransferTxCmd returns the command to create a NewMsgTransfer transaction +func GetTransferTxCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "transfer [src-port] [src-channel] [receiver] [amount]", + Short: "Transfer fungible token through IBC", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + ctx := context.NewCLIContext().WithCodec(cdc).WithBroadcastMode(flags.BroadcastBlock) + + sender := ctx.GetFromAddress() + srcPort := args[0] + srcChannel := args[1] + receiver, err := sdk.AccAddressFromBech32(args[2]) + if err != nil { + return err + } + + // parse coin trying to be sent + coins, err := sdk.ParseCoins(args[3]) + if err != nil { + return err + } + + source := viper.GetBool(FlagSource) + + msg := types.NewMsgTransfer(srcPort, srcChannel, coins, sender, receiver, source) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(ctx, txBldr, []sdk.Msg{msg}) + }, + } + cmd.Flags().Bool(FlagSource, false, "Pass flag for sending token from the source chain") + cmd.Flags().String(flags.FlagFrom, "", "key in local keystore to send from") + return cmd +} + +// GetMsgRecvPacketCmd returns the command to create a MsgRecvTransferPacket transaction +func GetMsgRecvPacketCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "recv-packet [sending-port-id] [sending-channel-id] [client-id]", + Short: "Creates and sends a SendPacket message", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc).WithBroadcastMode(flags.BroadcastBlock) + + node2 := viper.GetString(FlagNode2) + cid1 := viper.GetString(flags.FlagChainID) + cid2 := viper.GetString(FlagChainId2) + cliCtx2 := context.NewCLIContextIBC(cliCtx.GetFromAddress().String(), cid2, node2). + WithCodec(cdc). + WithBroadcastMode(flags.BroadcastBlock) + + header, err := clientutils.GetTendermintHeader(cliCtx2) + if err != nil { + return err + } + + sourcePort, sourceChannel, clientid := args[0], args[1], args[2] + + passphrase, err := keys.GetPassphrase(viper.GetString(flags.FlagFrom)) + if err != nil { + return nil + } + + viper.Set(flags.FlagChainID, cid1) + msgUpdateClient := clienttypes.NewMsgUpdateClient(clientid, header, cliCtx.GetFromAddress()) + if err := msgUpdateClient.ValidateBasic(); err != nil { + return err + } + + res, err := utils.CompleteAndBroadcastTx(txBldr, cliCtx, []sdk.Msg{msgUpdateClient}, passphrase) + if err != nil || !res.IsOK() { + return err + } + + viper.Set(flags.FlagChainID, cid2) + sequence := uint64(viper.GetInt(FlagSequence)) + packetRes, err := channelutils.QueryPacket(cliCtx2.WithHeight(header.Height-1), sourcePort, sourceChannel, sequence, uint64(viper.GetInt(FlagTimeout)), "ibc") + if err != nil { + return err + } + viper.Set(flags.FlagChainID, cid1) + + msg := types.NewMsgRecvPacket(packetRes.Packet, []commitment.Proof{packetRes.Proof}, packetRes.ProofHeight, cliCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd = client.PostCommands(cmd)[0] + cmd.Flags().Bool(FlagSource, false, "Pass flag for sending token from the source chain") + cmd.Flags().String(FlagNode2, "tcp://localhost:26657", "RPC port for the second chain") + cmd.Flags().String(FlagChainId2, "", "chain-id for the second chain") + cmd.Flags().String(FlagSequence, "", "sequence for the packet") + cmd.Flags().String(FlagTimeout, "", "timeout for the packet") + return cmd +} diff --git a/x/ibc/20-transfer/genesis.go b/x/ibc/20-transfer/genesis.go new file mode 100644 index 000000000000..facde722a27d --- /dev/null +++ b/x/ibc/20-transfer/genesis.go @@ -0,0 +1,17 @@ +package transfer + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" +) + +// InitGenesis sets distribution information for genesis +func InitGenesis(ctx sdk.Context, keeper Keeper) { + // check if the module account exists + moduleAcc := keeper.GetTransferAccount(ctx) + if moduleAcc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.GetModuleAccountName())) + } +} diff --git a/x/ibc/20-transfer/handler.go b/x/ibc/20-transfer/handler.go new file mode 100644 index 000000000000..731a6c27e7f5 --- /dev/null +++ b/x/ibc/20-transfer/handler.go @@ -0,0 +1,43 @@ +package transfer + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" +) + +// HandleMsgTransfer defines the sdk.Handler for MsgTransfer +func HandleMsgTransfer(ctx sdk.Context, k Keeper, msg MsgTransfer) (res sdk.Result) { + err := k.SendTransfer(ctx, msg.SourcePort, msg.SourceChannel, msg.Amount, msg.Sender, msg.Receiver, msg.Source) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver.String()), + ), + ) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgRecvPacket defines the sdk.Handler for MsgRecvPacket +func HandleMsgRecvPacket(ctx sdk.Context, k Keeper, msg MsgRecvPacket) (res sdk.Result) { + err := k.ReceivePacket(ctx, msg.Packet, msg.Proofs[0], msg.Height) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + ) + + return sdk.Result{Events: ctx.EventManager().Events()} +} diff --git a/x/ibc/20-transfer/keeper/callbacks.go b/x/ibc/20-transfer/keeper/callbacks.go new file mode 100644 index 000000000000..8594783e6456 --- /dev/null +++ b/x/ibc/20-transfer/keeper/callbacks.go @@ -0,0 +1,184 @@ +package keeper + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" +) + +// nolint: unused +func (k Keeper) onChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) error { + if order != channeltypes.UNORDERED { + return types.ErrInvalidChannelOrder(k.codespace, order.String()) + } + + if counterparty.PortID != types.BoundPortID { + return types.ErrInvalidPort(k.codespace, portID) + } + + if strings.TrimSpace(version) != "" { + return types.ErrInvalidVersion(k.codespace, fmt.Sprintf("invalid version: %s", version)) + } + + // NOTE: as the escrow address is generated from both the port and channel IDs + // there's no need to store it on a map. + return nil +} + +// nolint: unused +func (k Keeper) onChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + version string, + counterpartyVersion string, +) error { + if order != channeltypes.UNORDERED { + return types.ErrInvalidChannelOrder(k.codespace, order.String()) + } + + if counterparty.PortID != types.BoundPortID { + return types.ErrInvalidPort(k.codespace, portID) + } + + if strings.TrimSpace(version) != "" { + return types.ErrInvalidVersion(k.codespace, fmt.Sprintf("invalid version: %s", version)) + } + + if strings.TrimSpace(counterpartyVersion) != "" { + return types.ErrInvalidVersion(k.codespace, fmt.Sprintf("invalid counterparty version: %s", version)) + } + + // NOTE: as the escrow address is generated from both the port and channel IDs + // there's no need to store it on a map. + return nil +} + +// nolint: unused +func (k Keeper) onChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + version string, +) error { + if strings.TrimSpace(version) != "" { + return types.ErrInvalidVersion(k.codespace, fmt.Sprintf("invalid version: %s", version)) + } + + return nil +} + +// nolint: unused +func (k Keeper) onChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // no-op + return nil +} + +// nolint: unused +func (k Keeper) onChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // no-op + return nil +} + +// nolint: unused +func (k Keeper) onChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // no-op + return nil +} + +// onRecvPacket is called when an FTTransfer packet is received +// nolint: unused +func (k Keeper) onRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) error { + var data types.PacketData + + err := data.UnmarshalJSON(packet.GetData()) + if err != nil { + return types.ErrInvalidPacketData(k.codespace) + } + + return k.ReceiveTransfer( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), + packet.GetDestPort(), packet.GetDestChannel(), data, + ) +} + +// nolint: unused +func (k Keeper) onAcknowledgePacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) error { + // no-op + return nil +} + +// nolint: unused +func (k Keeper) onTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) error { + var data types.PacketData + + err := data.UnmarshalJSON(packet.GetData()) + if err != nil { + return types.ErrInvalidPacketData(k.codespace) + } + + // check the denom prefix + prefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourcePort()) + coins := make(sdk.Coins, len(data.Amount)) + for i, coin := range data.Amount { + coin := coin + if !strings.HasPrefix(coin.Denom, prefix) { + return sdk.ErrInvalidCoins(fmt.Sprintf("%s doesn't contain the prefix '%s'", coin.Denom, prefix)) + } + coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount) + } + + if data.Source { + escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) + return k.bankKeeper.SendCoins(ctx, escrowAddress, data.Sender, coins) + } + + // mint from supply + err = k.supplyKeeper.MintCoins(ctx, types.GetModuleAccountName(), data.Amount) + if err != nil { + return err + } + + return k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.GetModuleAccountName(), data.Sender, data.Amount) +} + +// nolint: unused +func (k Keeper) onTimeoutPacketClose(_ sdk.Context, _ channeltypes.Packet) { + panic("can't happen, only unordered channels allowed") +} diff --git a/x/ibc/20-transfer/keeper/keeper.go b/x/ibc/20-transfer/keeper/keeper.go new file mode 100644 index 000000000000..95576d4609f3 --- /dev/null +++ b/x/ibc/20-transfer/keeper/keeper.go @@ -0,0 +1,68 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +// DefaultPacketTimeout is the default packet timeout relative to the current block height +const ( + DefaultPacketTimeout = 1000 // NOTE: in blocks +) + +// Keeper defines the IBC transfer keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + codespace sdk.CodespaceType + prefix []byte // prefix bytes for accessing the store + + clientKeeper types.ClientKeeper + connectionKeeper types.ConnectionKeeper + channelKeeper types.ChannelKeeper + bankKeeper types.BankKeeper + supplyKeeper types.SupplyKeeper +} + +// NewKeeper creates a new IBC transfer Keeper instance +func NewKeeper( + cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType, + clientKeeper types.ClientKeeper, connnectionKeeper types.ConnectionKeeper, + channelKeeper types.ChannelKeeper, bankKeeper types.BankKeeper, + supplyKeeper types.SupplyKeeper, +) Keeper { + + // ensure ibc transfer module account is set + if addr := supplyKeeper.GetModuleAddress(types.GetModuleAccountName()); addr == nil { + panic("the IBC transfer module account has not been set") + } + + return Keeper{ + storeKey: key, + cdc: cdc, + codespace: sdk.CodespaceType(fmt.Sprintf("%s/%s", codespace, types.DefaultCodespace)), // "ibc/transfer", + prefix: []byte(types.SubModuleName + "/"), // "transfer/" + clientKeeper: clientKeeper, + connectionKeeper: connnectionKeeper, + channelKeeper: channelKeeper, + bankKeeper: bankKeeper, + supplyKeeper: supplyKeeper, + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s/%s", ibctypes.ModuleName, types.SubModuleName)) +} + +// GetTransferAccount returns the ICS20 - transfers ModuleAccount +func (k Keeper) GetTransferAccount(ctx sdk.Context) supplyexported.ModuleAccountI { + return k.supplyKeeper.GetModuleAccount(ctx, types.GetModuleAccountName()) +} diff --git a/x/ibc/20-transfer/keeper/keeper_test.go b/x/ibc/20-transfer/keeper/keeper_test.go new file mode 100644 index 000000000000..77c423052098 --- /dev/null +++ b/x/ibc/20-transfer/keeper/keeper_test.go @@ -0,0 +1,35 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/keeper" +) + +type KeeperTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + keeper *keeper.Keeper +} + +func (suite *KeeperTestSuite) SetupTest() { + isCheckTx := false + app := simapp.Setup(isCheckTx) + + suite.cdc = app.Codec() + suite.ctx = app.BaseApp.NewContext(isCheckTx, abci.Header{}) + suite.keeper = &app.IBCKeeper.TransferKeeper +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/ibc/20-transfer/keeper/relay.go b/x/ibc/20-transfer/keeper/relay.go new file mode 100644 index 000000000000..d87a589bbd80 --- /dev/null +++ b/x/ibc/20-transfer/keeper/relay.go @@ -0,0 +1,193 @@ +package keeper + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// SendTransfer handles transfer sending logic +func (k Keeper) SendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + amount sdk.Coins, + sender, + receiver sdk.AccAddress, + isSourceChain bool, +) error { + // get the port and channel of the counterparty + channel, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) + if !found { + return channeltypes.ErrChannelNotFound(k.codespace, sourcePort, sourceChannel) + } + + destinationPort := channel.Counterparty.PortID + destinationChannel := channel.Counterparty.ChannelID + + // get the next sequence + sequence, found := k.channelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel) + if !found { + return channeltypes.ErrSequenceNotFound(k.codespace, "send") + } + + coins := make(sdk.Coins, len(amount)) + prefix := types.GetDenomPrefix(destinationPort, destinationChannel) + switch { + case isSourceChain: + // build the receiving denomination prefix + for i, coin := range amount { + coins[i] = sdk.NewCoin(prefix+coin.Denom, coin.Amount) + } + default: + coins = amount + } + + return k.createOutgoingPacket(ctx, sequence, sourcePort, sourceChannel, destinationPort, destinationChannel, coins, sender, receiver, isSourceChain) +} + +// ReceivePacket handles receiving packet +func (k Keeper) ReceivePacket(ctx sdk.Context, packet channelexported.PacketI, proof commitment.ProofI, height uint64) error { + _, err := k.channelKeeper.RecvPacket(ctx, packet, proof, height, nil, k.storeKey) + if err != nil { + return err + } + + var data types.PacketData + err = data.UnmarshalJSON(packet.GetData()) + if err != nil { + return sdk.NewError(types.DefaultCodespace, types.CodeInvalidPacketData, "invalid packet data") + } + + return k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestPort(), packet.GetDestChannel(), data) +} + +// ReceiveTransfer handles transfer receiving logic +func (k Keeper) ReceiveTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel string, + data types.PacketData, +) error { + if data.Source { + prefix := types.GetDenomPrefix(destinationPort, destinationChannel) + for _, coin := range data.Amount { + if !strings.HasPrefix(coin.Denom, prefix) { + return sdk.ErrInvalidCoins(fmt.Sprintf("%s doesn't contain the prefix '%s'", coin.Denom, prefix)) + } + } + + // mint new tokens if the source of the transfer is the same chain + err := k.supplyKeeper.MintCoins(ctx, types.GetModuleAccountName(), data.Amount) + if err != nil { + return err + } + + // send to receiver + return k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.GetModuleAccountName(), data.Receiver, data.Amount) + } + + // unescrow tokens + + // check the denom prefix + prefix := types.GetDenomPrefix(sourcePort, sourceChannel) + coins := make(sdk.Coins, len(data.Amount)) + for i, coin := range data.Amount { + if !strings.HasPrefix(coin.Denom, prefix) { + return sdk.ErrInvalidCoins(fmt.Sprintf("%s doesn't contain the prefix '%s'", coin.Denom, prefix)) + } + coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount) + } + + escrowAddress := types.GetEscrowAddress(destinationPort, destinationChannel) + return k.bankKeeper.SendCoins(ctx, escrowAddress, data.Receiver, coins) + +} + +func (k Keeper) createOutgoingPacket( + ctx sdk.Context, + seq uint64, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel string, + amount sdk.Coins, + sender sdk.AccAddress, + receiver sdk.AccAddress, + isSourceChain bool, +) error { + if isSourceChain { + // escrow tokens if the destination chain is the same as the sender's + escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) + + prefix := types.GetDenomPrefix(destinationPort, destinationChannel) + coins := make(sdk.Coins, len(amount)) + for i, coin := range amount { + if !strings.HasPrefix(coin.Denom, prefix) { + return sdk.ErrInvalidCoins(fmt.Sprintf("%s doesn't contain the prefix '%s'", coin.Denom, prefix)) + } + coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount) + } + + err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, coins) + if err != nil { + return err + } + + } else { + // burn vouchers from the sender's balance if the source is from another chain + prefix := types.GetDenomPrefix(sourcePort, sourceChannel) + for _, coin := range amount { + if !strings.HasPrefix(coin.Denom, prefix) { + return sdk.ErrInvalidCoins(fmt.Sprintf("%s doesn't contain the prefix '%s'", coin.Denom, prefix)) + } + } + + // transfer the coins to the module account and burn them + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.GetModuleAccountName(), amount) + if err != nil { + return err + } + + // burn from supply + err = k.supplyKeeper.BurnCoins(ctx, types.GetModuleAccountName(), amount) + if err != nil { + return err + } + } + + packetData := types.PacketData{ + Amount: amount, + Sender: sender, + Receiver: receiver, + Source: isSourceChain, + } + + // TODO: This should be binary-marshaled and hashed (for the commitment in the store). + packetDataBz, err := packetData.MarshalJSON() + if err != nil { + return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeInvalidPacketData, "invalid packet data") + } + + packet := channeltypes.NewPacket( + seq, + uint64(ctx.BlockHeight())+DefaultPacketTimeout, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + packetDataBz, + ) + + // TODO: Remove this, capability keys are never generated when sending packets. Not sure why this is here. + key := sdk.NewKVStoreKey(types.BoundPortID) + + return k.channelKeeper.SendPacket(ctx, packet, key) +} diff --git a/x/ibc/20-transfer/keeper/relay_test.go b/x/ibc/20-transfer/keeper/relay_test.go new file mode 100644 index 000000000000..e6aa98f9eacd --- /dev/null +++ b/x/ibc/20-transfer/keeper/relay_test.go @@ -0,0 +1,6 @@ +package keeper_test + +func (suite *KeeperTestSuite) TestSendTransfer() { + err := suite.keeper.SendTransfer(suite.ctx, "", "", nil, nil, nil, true) + suite.Error(err) +} diff --git a/x/ibc/20-transfer/module.go b/x/ibc/20-transfer/module.go new file mode 100644 index 000000000000..269be2377f6e --- /dev/null +++ b/x/ibc/20-transfer/module.go @@ -0,0 +1,18 @@ +package transfer + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/client/cli" +) + +// Name returns the IBC transfer ICS name +func Name() string { + return SubModuleName +} + +// GetTxCmd returns the root tx command for the IBC transfer. +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(cdc) +} diff --git a/x/ibc/20-transfer/types/codec.go b/x/ibc/20-transfer/types/codec.go new file mode 100644 index 000000000000..ba018b3fc676 --- /dev/null +++ b/x/ibc/20-transfer/types/codec.go @@ -0,0 +1,21 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgTransfer{}, "ibc/transfer/MsgTransfer", nil) + cdc.RegisterConcrete(MsgRecvPacket{}, "ibc/transfer/MsgRecvPacket", nil) + cdc.RegisterConcrete(PacketData{}, "ibc/transfer/PacketData", nil) +} + +var ModuleCdc = codec.New() + +func init() { + RegisterCodec(ModuleCdc) + channel.RegisterCodec(ModuleCdc) + commitment.RegisterCodec(ModuleCdc) +} diff --git a/x/ibc/20-transfer/types/errors.go b/x/ibc/20-transfer/types/errors.go new file mode 100644 index 000000000000..51f95d560a01 --- /dev/null +++ b/x/ibc/20-transfer/types/errors.go @@ -0,0 +1,51 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// transfer error codes +const ( + DefaultCodespace sdk.CodespaceType = SubModuleName + + CodeInvalidAddress sdk.CodeType = 101 + CodeErrSendPacket sdk.CodeType = 102 + CodeInvalidPacketData sdk.CodeType = 103 + CodeInvalidChannelOrder sdk.CodeType = 104 + CodeInvalidPort sdk.CodeType = 105 + CodeInvalidVersion sdk.CodeType = 106 + CodeProofMissing sdk.CodeType = 107 + CodeInvalidHeight sdk.CodeType = 108 +) + +// ErrInvalidAddress implements sdk.Error +func ErrInvalidAddress(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidAddress, msg) +} + +// ErrSendPacket implements sdk.Error +func ErrSendPacket(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeErrSendPacket, "failed to send packet") +} + +// ErrInvalidPacketData implements sdk.Error +func ErrInvalidPacketData(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidPacketData, "invalid packet data") +} + +// ErrInvalidChannelOrder implements sdk.Error +func ErrInvalidChannelOrder(codespace sdk.CodespaceType, order string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidChannelOrder, fmt.Sprintf("invalid channel order: %s", order)) +} + +// ErrInvalidPort implements sdk.Error +func ErrInvalidPort(codespace sdk.CodespaceType, portID string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidPort, fmt.Sprintf("invalid port ID: %s", portID)) +} + +// ErrInvalidVersion implements sdk.Error +func ErrInvalidVersion(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVersion, msg) +} diff --git a/x/ibc/20-transfer/types/events.go b/x/ibc/20-transfer/types/events.go new file mode 100644 index 000000000000..759548ae1827 --- /dev/null +++ b/x/ibc/20-transfer/types/events.go @@ -0,0 +1,17 @@ +package types + +import ( + "fmt" + + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// IBC transfer events +const ( + AttributeKeyReceiver = "receiver" +) + +// IBC transfer events vars +var ( + AttributeValueCategory = fmt.Sprintf("%s_%s", ibctypes.ModuleName, SubModuleName) +) diff --git a/x/ibc/20-transfer/types/expected_keepers.go b/x/ibc/20-transfer/types/expected_keepers.go new file mode 100644 index 000000000000..252eb8060645 --- /dev/null +++ b/x/ibc/20-transfer/types/expected_keepers.go @@ -0,0 +1,44 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +// BankKeeper defines the expected bank keeper +type BankKeeper interface { + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error +} + +// ChannelKeeper defines the expected IBC channel keeper +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channel.Channel, found bool) + GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) + SendPacket(ctx sdk.Context, packet channelexported.PacketI, portCapability sdk.CapabilityKey) error + RecvPacket(ctx sdk.Context, packet channelexported.PacketI, proof commitment.ProofI, proofHeight uint64, acknowledgement []byte, portCapability sdk.CapabilityKey) (channelexported.PacketI, error) +} + +// ClientKeeper defines the expected IBC client keeper +type ClientKeeper interface { + GetConsensusState(ctx sdk.Context, clientID string) (connection clientexported.ConsensusState, found bool) +} + +// ConnectionKeeper defines the expected IBC connection keeper +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connection connection.ConnectionEnd, found bool) +} + +// SupplyKeeper expected supply keeper +type SupplyKeeper interface { + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) sdk.Error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) sdk.Error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error +} diff --git a/x/ibc/20-transfer/types/keys.go b/x/ibc/20-transfer/types/keys.go new file mode 100644 index 000000000000..09b54dcadea5 --- /dev/null +++ b/x/ibc/20-transfer/types/keys.go @@ -0,0 +1,46 @@ +package types + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +const ( + // SubModuleName defines the IBC transfer name + SubModuleName = "transfer" + + // StoreKey is the store key string for IBC transfer + StoreKey = SubModuleName + + // RouterKey is the message route for IBC transfer + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC transfer + QuerierRoute = SubModuleName + + // BoundPortID defines the name of the capability key + BoundPortID = "bank" +) + +// GetEscrowAddress returns the escrow address for the specified channel +// +// CONTRACT: this assumes that there's only one bank bridge module that owns the +// port associated with the channel ID so that the address created is actually +// unique. +func GetEscrowAddress(portID, channelID string) sdk.AccAddress { + return sdk.AccAddress(crypto.AddressHash([]byte(portID + channelID))) +} + +// GetDenomPrefix returns the receiving denomination prefix +func GetDenomPrefix(portID, channelID string) string { + return fmt.Sprintf("%s/%s/", portID, channelID) +} + +// GetModuleAccountName returns the IBC transfer module account name for supply +func GetModuleAccountName() string { + return fmt.Sprintf("%s/%s", ibctypes.ModuleName, SubModuleName) +} diff --git a/x/ibc/20-transfer/types/msgs.go b/x/ibc/20-transfer/types/msgs.go new file mode 100644 index 000000000000..506676e66d7e --- /dev/null +++ b/x/ibc/20-transfer/types/msgs.go @@ -0,0 +1,138 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +type MsgTransfer struct { + SourcePort string `json:"source_port" yaml:"source_port"` // the port on which the packet will be sent + SourceChannel string `json:"source_channel" yaml:"source_channel"` // the channel by which the packet will be sent + Amount sdk.Coins `json:"amount" yaml:"amount"` // the tokens to be transferred + Sender sdk.AccAddress `json:"sender" yaml:"sender"` // the sender address + Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` // the recipient address on the destination chain + Source bool `json:"source" yaml:"source"` // indicates if the sending chain is the source chain of the tokens to be transferred +} + +// NewMsgTransfer creates a new MsgTransfer instance +func NewMsgTransfer( + sourcePort, sourceChannel string, amount sdk.Coins, sender, receiver sdk.AccAddress, source bool, +) MsgTransfer { + return MsgTransfer{ + SourcePort: sourcePort, + SourceChannel: sourceChannel, + Amount: amount, + Sender: sender, + Receiver: receiver, + Source: source, + } +} + +// Route implements sdk.Msg +func (MsgTransfer) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (MsgTransfer) Type() string { + return "transfer" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgTransfer) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.SourcePort); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultPortIdentifierValidator(msg.SourceChannel); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins("transfer amount is invalid") + } + if !msg.Amount.IsAllPositive() { + return sdk.ErrInsufficientCoins("transfer amount must be positive") + } + if msg.Sender.Empty() { + return sdk.ErrInvalidAddress("missing sender address") + } + if msg.Receiver.Empty() { + return sdk.ErrInvalidAddress("missing recipient address") + } + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgTransfer) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgTransfer) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +type MsgRecvPacket struct { + Packet channelexported.PacketI `json:"packet" yaml:"packet"` + Proofs []commitment.Proof `json:"proofs" yaml:"proofs"` + Height uint64 `json:"height" yaml:"height"` + Signer sdk.AccAddress `json:"signer" yaml:"signer"` +} + +// NewMsgRecvPacket creates a new MsgRecvPacket instance +func NewMsgRecvPacket(packet channelexported.PacketI, proofs []commitment.Proof, height uint64, signer sdk.AccAddress) MsgRecvPacket { + return MsgRecvPacket{ + Packet: packet, + Proofs: proofs, + Height: height, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (MsgRecvPacket) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (MsgRecvPacket) Type() string { + return "recv_packet" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgRecvPacket) ValidateBasic() sdk.Error { + if msg.Height < 1 { + return sdk.NewError(DefaultCodespace, CodeInvalidHeight, "invalid height") + } + + if msg.Proofs == nil { + return sdk.NewError(DefaultCodespace, CodeProofMissing, "proof missing") + } + + for _, proof := range msg.Proofs { + if proof.Proof == nil { + return sdk.NewError(DefaultCodespace, CodeProofMissing, "proof missing") + } + } + + if msg.Signer.Empty() { + return sdk.NewError(DefaultCodespace, CodeInvalidAddress, "invalid signer") + } + + return msg.Packet.ValidateBasic() +} + +// GetSignBytes implements sdk.Msg +func (msg MsgRecvPacket) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgRecvPacket) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} diff --git a/x/ibc/20-transfer/types/packet.go b/x/ibc/20-transfer/types/packet.go new file mode 100644 index 000000000000..a9df673cc19f --- /dev/null +++ b/x/ibc/20-transfer/types/packet.go @@ -0,0 +1,70 @@ +package types + +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// PacketData defines a struct for the packet payload +type PacketData struct { + Amount sdk.Coins `json:"amount" yaml:"amount"` // the tokens to be transferred + Sender sdk.AccAddress `json:"sender" yaml:"sender"` // the sender address + Receiver sdk.AccAddress `json:"receiver" yaml:"receiver"` // the recipient address on the destination chain + Source bool `json:"source" yaml:"source"` // indicates if the sending chain is the source chain of the tokens to be transferred +} + +func (pd PacketData) MarshalAmino() ([]byte, error) { + return ModuleCdc.MarshalBinaryBare(pd) +} + +func (pd *PacketData) UnmarshalAmino(bz []byte) (err error) { + return ModuleCdc.UnmarshalBinaryBare(bz, pd) +} + +func (pd PacketData) Marshal() []byte { + return ModuleCdc.MustMarshalBinaryBare(pd) +} + +type PacketDataAlias PacketData + +// MarshalJSON implements the json.Marshaler interface. +func (pd PacketData) MarshalJSON() ([]byte, error) { + return json.Marshal((PacketDataAlias)(pd)) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (pd *PacketData) UnmarshalJSON(bz []byte) (err error) { + return json.Unmarshal(bz, (*PacketDataAlias)(pd)) +} + +func (pd PacketData) String() string { + return fmt.Sprintf(`PacketData: + Amount: %s + Sender: %s + Receiver: %s + Source: %v`, + pd.Amount.String(), + pd.Sender, + pd.Receiver, + pd.Source, + ) +} + +// ValidateBasic performs a basic check of the packet fields +func (pd PacketData) ValidateBasic() sdk.Error { + if !pd.Amount.IsValid() { + return sdk.ErrInvalidCoins("transfer amount is invalid") + } + if !pd.Amount.IsAllPositive() { + return sdk.ErrInsufficientCoins("transfer amount must be positive") + } + if pd.Sender.Empty() { + return sdk.ErrInvalidAddress("missing sender address") + } + if pd.Receiver.Empty() { + return sdk.ErrInvalidAddress("missing recipient address") + } + return nil +} diff --git a/x/ibc/alias.go b/x/ibc/alias.go index 7c1ad02349e2..abb2aae3c59a 100644 --- a/x/ibc/alias.go +++ b/x/ibc/alias.go @@ -12,10 +12,11 @@ import ( ) const ( - ModuleName = types.ModuleName - StoreKey = types.StoreKey - QuerierRoute = types.QuerierRoute - RouterKey = types.RouterKey + ModuleName = types.ModuleName + StoreKey = types.StoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey + DefaultCodespace = types.DefaultCodespace ) var ( diff --git a/x/ibc/ante.go b/x/ibc/ante.go new file mode 100644 index 000000000000..0e1f7f635d70 --- /dev/null +++ b/x/ibc/ante.go @@ -0,0 +1,51 @@ +package ibc + +// // TODO: Should extract timeout msgs too +// func ExtractMsgPackets(msgs []sdk.Msg) (res []MsgPacket, abort bool) { +// res = make([]MsgPacket, 0, len(msgs)) +// for _, msg := range msgs { +// msgp, ok := msg.(MsgPacket) +// if ok { +// res = append(res, msgp) +// } +// } + +// if len(res) >= 2 { +// first := res[0] +// for _, msg := range res[1:] { +// if len(msg.ChannelID) != 0 && msg.ChannelID != first.ChannelID { +// return res, true +// } +// msg.ChannelID = first.ChannelID +// } +// } + +// return +// } + +// func VerifyMsgPackets(ctx sdk.Context, channel channel.Manager, msgs []MsgPacket) error { +// for _, msg := range msgs { +// err := channel.Receive(ctx, msg.Proofs, msg.Height, msg.ReceiverPort(), msg.ChannelID, msg.Packet) +// if err != nil { +// return err +// } +// } + +// return nil +// } + +// func NewAnteDecorator(channel channel.Manager) sdk.AnteDecorator { +// return func(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { +// msgs, abort := ExtractMsgPackets(tx.GetMsgs()) +// if abort { +// return ctx, host.ErrInvalidPacket +// } + +// err := VerifyMsgPackets(ctx, channel, msgs) +// if err != nil { +// return ctx, sdkerrors.Wrap(host.ErrInvalidPacket, err.Error()) +// } + +// return next(ctx, tx, simulate) +// } +// } diff --git a/x/ibc/client/cli/cli.go b/x/ibc/client/cli/cli.go index 04289628f640..cc6bd3bd7822 100644 --- a/x/ibc/client/cli/cli.go +++ b/x/ibc/client/cli/cli.go @@ -7,6 +7,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ibcclient "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer" "github.com/cosmos/cosmos-sdk/x/ibc/types" ) @@ -23,6 +25,8 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { ibcTxCmd.AddCommand(client.PostCommands( ibcclient.GetTxCmd(cdc, storeKey), connection.GetTxCmd(cdc, storeKey), + channel.GetTxCmd(cdc, storeKey), + transfer.GetTxCmd(cdc), )...) return ibcTxCmd } diff --git a/x/ibc/handler.go b/x/ibc/handler.go index b80267dd7d92..07fc9f831be8 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -7,6 +7,7 @@ import ( client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer" ) // NewHandler defines the IBC handler @@ -57,6 +58,13 @@ func NewHandler(k Keeper) sdk.Handler { case channel.MsgChannelCloseConfirm: return channel.HandleMsgChannelCloseConfirm(ctx, k.ChannelKeeper, msg) + // IBC transfer msgs + case transfer.MsgTransfer: + return transfer.HandleMsgTransfer(ctx, k.TransferKeeper, msg) + + case transfer.MsgRecvPacket: + return transfer.HandleMsgRecvPacket(ctx, k.TransferKeeper, msg) + default: errMsg := fmt.Sprintf("unrecognized IBC message type: %T", msg) return sdk.ErrUnknownRequest(errMsg).Result() diff --git a/x/ibc/keeper/keeper.go b/x/ibc/keeper/keeper.go index 52158b48b140..2145416efb55 100644 --- a/x/ibc/keeper/keeper.go +++ b/x/ibc/keeper/keeper.go @@ -7,6 +7,7 @@ import ( connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" port "github.com/cosmos/cosmos-sdk/x/ibc/05-port" + transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer" ) // Keeper defines each ICS keeper for IBC @@ -15,19 +16,25 @@ type Keeper struct { ConnectionKeeper connection.Keeper ChannelKeeper channel.Keeper PortKeeper port.Keeper + TransferKeeper transfer.Keeper } // NewKeeper creates a new ibc Keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) Keeper { +func NewKeeper( + cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType, + bk transfer.BankKeeper, sk transfer.SupplyKeeper, +) Keeper { clientKeeper := client.NewKeeper(cdc, key, codespace) connectionKeeper := connection.NewKeeper(cdc, key, codespace, clientKeeper) portKeeper := port.NewKeeper(cdc, key, codespace) channelKeeper := channel.NewKeeper(cdc, key, codespace, clientKeeper, connectionKeeper, portKeeper) + transferKeeper := transfer.NewKeeper(cdc, key, codespace, clientKeeper, connectionKeeper, channelKeeper, bk, sk) return Keeper{ ClientKeeper: clientKeeper, ConnectionKeeper: connectionKeeper, ChannelKeeper: channelKeeper, PortKeeper: portKeeper, + TransferKeeper: transferKeeper, } } diff --git a/x/ibc/keeper/keeper_test.go b/x/ibc/keeper/keeper_test.go new file mode 100644 index 000000000000..4aee65b6db9e --- /dev/null +++ b/x/ibc/keeper/keeper_test.go @@ -0,0 +1,37 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/keeper" +) + +type KeeperTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + keeper *keeper.Keeper + querier sdk.Querier +} + +func (suite *KeeperTestSuite) SetupTest() { + isCheckTx := false + app := simapp.Setup(isCheckTx) + + suite.cdc = app.Codec() + suite.ctx = app.BaseApp.NewContext(isCheckTx, abci.Header{}) + suite.keeper = &app.IBCKeeper + suite.querier = keeper.NewQuerier(app.IBCKeeper) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/ibc/keeper/querier.go b/x/ibc/keeper/querier.go index fc2527df104c..ee934fabd018 100644 --- a/x/ibc/keeper/querier.go +++ b/x/ibc/keeper/querier.go @@ -26,7 +26,6 @@ func NewQuerier(k Keeper) sdk.Querier { default: return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown IBC %s query endpoint", client.SubModuleName)) } - case connection.SubModuleName: switch path[1] { case connection.QueryConnection: @@ -36,7 +35,6 @@ func NewQuerier(k Keeper) sdk.Querier { default: return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown IBC %s query endpoint", connection.SubModuleName)) } - case channel.SubModuleName: switch path[1] { case channel.QueryChannel: @@ -44,7 +42,6 @@ func NewQuerier(k Keeper) sdk.Querier { default: return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown IBC %s query endpoint", channel.SubModuleName)) } - default: return nil, sdk.ErrUnknownRequest("unknown IBC query endpoint") } diff --git a/x/ibc/keeper/querier_test.go b/x/ibc/keeper/querier_test.go new file mode 100644 index 000000000000..62e37faac203 --- /dev/null +++ b/x/ibc/keeper/querier_test.go @@ -0,0 +1,102 @@ +package keeper_test + +import ( + "fmt" + + "github.com/stretchr/testify/require" + + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + + abci "github.com/tendermint/tendermint/abci/types" +) + +// TestNewQuerier tests that the querier paths are correct. +// NOTE: the actuall testing functionallity are located on each ICS querier test. +func (suite *KeeperTestSuite) TestNewQuerier() { + + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + cases := []struct { + name string + path []string + expectsDefaultErr bool + errMsg string + }{ + {"client - QueryClientState", + []string{client.SubModuleName, client.QueryClientState}, + false, + "", + }, + { + "client - QueryConsensusState", + []string{client.SubModuleName, client.QueryConsensusState}, + false, + "", + }, + { + "client - QueryVerifiedRoot", + []string{client.SubModuleName, client.QueryVerifiedRoot}, + false, + "", + }, + { + "client - invalid query", + []string{client.SubModuleName, "foo"}, + true, + fmt.Sprintf("unknown IBC %s query endpoint", client.SubModuleName), + }, + { + "connection - QueryConnection", + []string{connection.SubModuleName, connection.QueryConnection}, + false, + "", + }, + { + "connection - QueryClientConnections", + []string{connection.SubModuleName, connection.QueryClientConnections}, + false, + "", + }, + { + "connection - invalid query", + []string{connection.SubModuleName, "foo"}, + true, + fmt.Sprintf("unknown IBC %s query endpoint", connection.SubModuleName), + }, + { + "channel - QueryChannel", + []string{channel.SubModuleName, channel.QueryChannel}, + false, + "", + }, + { + "channel - invalid query", + []string{channel.SubModuleName, "foo"}, + true, + fmt.Sprintf("unknown IBC %s query endpoint", channel.SubModuleName), + }, + { + "invalid query", + []string{"foo"}, + true, + "unknown IBC query endpoint", + }, + } + + for i, tc := range cases { + i, tc := i, tc + suite.Run(tc.name, func() { + _, err := suite.querier(suite.ctx, tc.path, query) + if tc.expectsDefaultErr { + require.Contains(suite.T(), err.Error(), tc.errMsg, "test case #%d", i) + } else { + suite.Error(err, "test case #%d", i) + } + }) + } +} diff --git a/x/ibc/module.go b/x/ibc/module.go index 791517826f03..2e91d5540df0 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -15,6 +15,7 @@ import ( client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer" commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" "github.com/cosmos/cosmos-sdk/x/ibc/types" @@ -41,6 +42,7 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { client.RegisterCodec(cdc) connection.RegisterCodec(cdc) channel.RegisterCodec(cdc) + transfer.RegisterCodec(cdc) commitment.RegisterCodec(cdc) } @@ -116,6 +118,8 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { // InitGenesis performs genesis initialization for the staking module. It returns // no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + // check if the IBC transfer module account is set + transfer.InitGenesis(ctx, am.keeper.TransferKeeper) return []abci.ValidatorUpdate{} } diff --git a/x/ibc/types/types.go b/x/ibc/types/types.go index 535a8f2461e5..bce43911994f 100644 --- a/x/ibc/types/types.go +++ b/x/ibc/types/types.go @@ -1,15 +1,22 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + const ( // ModuleName is the name of the IBC module ModuleName = "ibc" // StoreKey is the string store representation - StoreKey = ModuleName + StoreKey string = ModuleName // QuerierRoute is the querier route for the IBC module - QuerierRoute = ModuleName + QuerierRoute string = ModuleName // RouterKey is the msg router key for the IBC module - RouterKey = ModuleName + RouterKey string = ModuleName + + // DefaultCodespace of the IBC module + DefaultCodespace sdk.CodespaceType = ModuleName )