Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS 20 implementation #5250

Merged
merged 24 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion x/ibc/04-channel/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const (
AttributeKeyReceiverPort = "receiver_port"
AttributeKeyChannelID = "channel_id"
AttributeKeySequence = "sequence"
AttributeKeyPacket = "packet"
)

// IBC channel events vars
Expand Down
26 changes: 26 additions & 0 deletions x/ibc/20-transfer/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package transfer

import (
"github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/kepper"
"github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types"
)

type (
MsgTransfer = types.MsgTransfer
TransferPacketData = types.TransferPacketData
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
Keeper = keeper.Keeper
)

const (
SubModuleName = types.SubModuleName
StoreKey = types.StoreKey
QuerierRoute = types.QuerierRoute
RouterKey = types.RouterKey
)

var (
RegisterCdc = types.RegisterCodec

NewKeeper = keeper.NewKeeper
NewMsgTransfer = types.NewMsgTransfer
)
74 changes: 74 additions & 0 deletions x/ibc/20-transfer/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cli

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/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/cosmos/cosmos-sdk/x/ibc/20-transfer/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// IBC transfer flags
var (
FlagSource = "source"
)

// 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),
)

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]
srcChan := args[1]
receiver := args[2]

// parse coin trying to be sent
coin, err := sdk.ParseCoin(args[3])
if err != nil {
return err
}

source := false
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
if viper.GetBool(FlagSource) {
chengwenxi marked this conversation as resolved.
Show resolved Hide resolved
source = true
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}
fedekunze marked this conversation as resolved.
Show resolved Hide resolved

msg := types.NewMsgTransfer(srcPort, srcChan, coin.Denom, coin.Amount, 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 = client.PostCommands(cmd)[0]

return cmd
}
25 changes: 25 additions & 0 deletions x/ibc/20-transfer/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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.SrcPort, msg.SrcChannel, msg.Denomination, msg.Amount, msg.Sender, msg.Receiver, msg.Source)
if err != nil {
return err.Result()
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(types.AttributeKeySender, msg.Sender.String()),
sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver),
sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(msg.Denomination, msg.Amount).String()),
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
))

return sdk.Result{Events: ctx.EventManager().Events()}
}
166 changes: 166 additions & 0 deletions x/ibc/20-transfer/kepper/keeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package keeper
chengwenxi marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"
"strings"

"github.com/cosmos/cosmos-sdk/codec"
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"
"github.com/tendermint/tendermint/crypto"
)

const (
DefaultPacketTimeout = 1000 // default packet timeout relative to the current block height
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cwgoes do you think we should use make this a param ?

)

// Keeper defines the IBC transfer keeper
type Keeper struct {
cdc *codec.Codec
key sdk.StoreKey
ck types.ChannelKeeper
bk types.BankKeeper
}

// NewKeeper creates a new IBC transfer Keeper instance
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck types.ChannelKeeper, bk types.BankKeeper) Keeper {
return Keeper{
cdc: cdc,
key: key,
ck: ck,
bk: bk,
}
}

// SendTransfer handles transfer sending logic
func (k Keeper) SendTransfer(ctx sdk.Context, srcPort, srcChan string, denom string, amount sdk.Int, sender sdk.AccAddress, receiver string, source bool) sdk.Error {
// get the port and channel of the counterparty
channel, ok := k.ck.GetChannel(ctx, srcPort, srcChan)
if !ok {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), channeltypes.CodeChannelNotFound, "failed to get channel")
}

destPort := channel.Counterparty.PortID
destChan := channel.Counterparty.ChannelID

// get the next sequence
sequence, ok := k.ck.GetNextSequenceSend(ctx, srcPort, srcChan)
if !ok {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), channeltypes.CodeSequenceNotFound, "failed to retrieve sequence")
}

if source {
// build the receiving denomination prefix
denom = getDenomPrefix(destPort, destChan) + denom
}

return k.createOutgoingPacket(ctx, sequence, srcPort, srcChan, destPort, destChan, denom, amount, sender, receiver, source)
}

// ReceiveTransfer handles transfer receiving logic
func (k Keeper) ReceiveTransfer(ctx sdk.Context, data types.TransferPacketData, destPort, destChannel, srcPort, srcChannel string) sdk.Error {

receiverAddr, err := sdk.AccAddressFromBech32(data.Receiver)
if err != nil {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeInvalidReceiver, "invalid receiver address")
}

if data.Source {
// mint tokens

// check the denom prefix
if !strings.HasPrefix(data.Denomination, getDenomPrefix(destPort, destChannel)) {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeIncorrectDenom, "incorrect denomination")
}

_, err := k.bk.AddCoins(ctx, receiverAddr, sdk.Coins{sdk.NewCoin(data.Denomination, data.Amount)})
if err != nil {
return err
}

} else {
// unescrow tokens

// check the denom prefix
prefix := getDenomPrefix(srcPort, srcChannel)
if !strings.HasPrefix(data.Denomination, prefix) {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeIncorrectDenom, "incorrect denomination")
}

escrowAddress := getEscrowAddress(destChannel)
err := k.bk.SendCoins(ctx, escrowAddress, receiverAddr, sdk.Coins{sdk.NewCoin(data.Denomination[len(prefix):], data.Amount)})
if err != nil {
return err
}
}

return nil
}

func (k Keeper) createOutgoingPacket(ctx sdk.Context, seq uint64, srcPort, srcChan, destPort, destChan string, denom string, amount sdk.Int, sender sdk.AccAddress, receiver string, source bool) sdk.Error {
if source {
// escrow tokens

// get escrow address
escrowAddress := getEscrowAddress(srcChan)

// check the denom prefix
prefix := getDenomPrefix(destPort, destChan)
if !strings.HasPrefix(denom, prefix) {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeIncorrectDenom, "incorrect denomination")
}

err := k.bk.SendCoins(ctx, sender, escrowAddress, sdk.Coins{sdk.NewCoin(denom[len(prefix):], amount)})
if err != nil {
return err
}

} else {
// burn vouchers from sender

// check the denom prefix
prefix := getDenomPrefix(srcPort, srcChan)
if !strings.HasPrefix(denom, prefix) {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeIncorrectDenom, "incorrect denomination")
}

_, err := k.bk.SubtractCoins(ctx, sender, sdk.Coins{sdk.NewCoin(denom, amount)})
if err != nil {
return err
}
}

// build packet
packetData := types.TransferPacketData{
Denomination: denom,
Amount: amount,
Sender: sender.String(),
Receiver: receiver,
Source: source,
}

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, srcPort, srcChan, destPort, destChan, packetDataBz)
Copy link

@baymax19 baymax19 Oct 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how ctx.BlockHeight() useful here ?, if destination chain blockheight > (ctx.blockheight+DefaultPacketTimeout)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the timeout block height should be the destination chain block height.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how ctx.BlockHeight() useful here ?, if destination chain blockheight > (ctx.blockheight+DefaultPacketTimeout)

How about using the latest consensus height of the counterparty?


err = k.ck.SendPacket(ctx, packet)
if err != nil {
return sdk.NewError(sdk.CodespaceType(types.DefaultCodespace), types.CodeErrSendPacket, "failed to send packet")
}

return nil
}

// getEscrowAddress returns the escrow address for the specified channel
func getEscrowAddress(chanID string) sdk.AccAddress {
return sdk.AccAddress(crypto.AddressHash([]byte(chanID)))
}

// getDenomPrefix returns the receiving denomination prefix
func getDenomPrefix(port, channel string) string {
return fmt.Sprintf("%s/%s", port, channel)
}
18 changes: 18 additions & 0 deletions x/ibc/20-transfer/module.go
Original file line number Diff line number Diff line change
@@ -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)
}
20 changes: 20 additions & 0 deletions x/ibc/20-transfer/types/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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(TransferPacketData{}, "ibc/transfer/TransferPacketData", nil)
}

var ModuleCdc = codec.New()

func init() {
RegisterCodec(ModuleCdc)
channel.RegisterCodec(ModuleCdc)
commitment.RegisterCodec(ModuleCdc)
}
17 changes: 17 additions & 0 deletions x/ibc/20-transfer/types/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// transfer error codes
const (
DefaultCodespace sdk.CodespaceType = SubModuleName

CodeIncorrectDenom sdk.CodeType = 101
CodeInvalidAmount sdk.CodeType = 102
CodeInvalidAddress sdk.CodeType = 103
CodeInvalidReceiver sdk.CodeType = 104
CodeErrSendPacket sdk.CodeType = 105
CodeInvalidPacketData sdk.CodeType = 106
chengwenxi marked this conversation as resolved.
Show resolved Hide resolved
)
20 changes: 20 additions & 0 deletions x/ibc/20-transfer/types/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package types

import (
"fmt"

transfer "github.com/cosmos/cosmos-sdk/x/ibc/20-transfer"
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
)

// IBC transfer events
const (
AttributeKeySender = "sender"
chengwenxi marked this conversation as resolved.
Show resolved Hide resolved
AttributeKeyReceiver = "receiver"
AttributeKeyAmount = "amount"
)

// IBC transfer events vars
var (
AttributeValueCategory = fmt.Sprintf("%s_%s", ibctypes.ModuleName, transfer.SubModuleName)
)
Loading