fix ibc-transfer source bug (#6865)

* update proto and refactor types test

* cleanup SendTransfer

* fix build and prep to update handler tests

* fix handler tests

* fix tests

* update spec

* fix lint

* Apply suggestions from code review

Co-authored-by: Aditya <adityasripal@gmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* rename amount to token

* merge send transfer and createOutgoingPacket

* apply rest of @fedekunze review suggestions

* apply review suggestions from @AdityaSripal

* update spec to token renaming

* split token in denom and amount when creating fungible token packet

* remove source flag, add prefix on recv

* fix lint

* fix proto numbers

* Update x/ibc-transfer/keeper/relay.go

Co-authored-by: Christopher Goes <cwgoes@pluranimity.org>

* apply @cwgoes review suggestions

* Apply suggestions from code review

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* apply @fedekunze suggestions

* Apply suggestions from code review

* fix lint

* apply @AdityaSripal review requests

* Update x/ibc-transfer/types/coin.go

* apply @AdityaSpripal last suggestions

Co-authored-by: Aditya <adityasripal@gmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Christopher Goes <cwgoes@pluranimity.org>
This commit is contained in:
colin axner 2020-08-01 11:16:22 +02:00 committed by GitHub
parent 8e67a5d23b
commit b3bbca343f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 544 additions and 427 deletions

View File

@ -6,26 +6,19 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types";
import "gogoproto/gogo.proto";
import "cosmos/cosmos.proto";
// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between ICS20 enabled chains.
// See ICS Spec here: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between
// ICS20 enabled chains. See ICS Spec here:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
message MsgTransfer {
// the port on which the packet will be sent
string source_port = 1 [
(gogoproto.moretags) = "yaml:\"source_port\""
];
string source_port = 1 [(gogoproto.moretags) = "yaml:\"source_port\""];
// the channel by which the packet will be sent
string source_channel = 2 [
(gogoproto.moretags) = "yaml:\"source_channel\""
];
string source_channel = 2 [(gogoproto.moretags) = "yaml:\"source_channel\""];
// the tokens to be transferred
repeated cosmos.Coin amount = 3 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
cosmos.Coin token = 3 [(gogoproto.nullable) = false];
// the sender address
bytes sender = 4 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"
];
bytes sender = 4
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
// the recipient address on the destination chain
string receiver = 5;
// Timeout height relative to the current block height.
@ -33,27 +26,29 @@ message MsgTransfer {
uint64 timeout_height = 6 [(gogoproto.moretags) = "yaml:\"timeout_height\""];
// Timeout timestamp (in nanoseconds) relative to the current block timestamp.
// The timeout is disabled when set to 0.
uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
uint64 timeout_timestamp = 7
[(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
}
// FungibleTokenPacketData defines a struct for the packet payload
// See FungibleTokenPacketData spec: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
// See FungibleTokenPacketData spec:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
message FungibleTokenPacketData {
// the tokens to be transferred
repeated cosmos.Coin amount = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// the token denomination to be transferred
string denom = 1;
// the token amount to be transferred
uint64 amount = 2;
// the sender address
string sender = 2;
string sender = 3;
// the recipient address on the destination chain
string receiver = 3;
string receiver = 4;
}
// FungibleTokenPacketAcknowledgement contains a boolean success flag and an optional error msg
// error msg is empty string on success
// See spec for onAcknowledgePacket: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
// FungibleTokenPacketAcknowledgement contains a boolean success flag and an
// optional error msg error msg is empty string on success See spec for
// onAcknowledgePacket:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
message FungibleTokenPacketAcknowledgement {
bool success = 1;
string error = 2;
bool success = 1;
string error = 2;
}

View File

@ -44,7 +44,7 @@ to the counterparty channel. Any timeout set to 0 is disabled.`),
srcChannel := args[1]
receiver := args[2]
coins, err := sdk.ParseCoins(args[3])
coin, err := sdk.ParseCoin(args[3])
if err != nil {
return err
}
@ -82,7 +82,7 @@ to the counterparty channel. Any timeout set to 0 is disabled.`),
}
msg := types.NewMsgTransfer(
srcPort, srcChannel, coins, sender, receiver, timeoutHeight, timeoutTimestamp,
srcPort, srcChannel, coin, sender, receiver, timeoutHeight, timeoutTimestamp,
)
if err := msg.ValidateBasic(); err != nil {
return err

View File

@ -21,7 +21,7 @@ func RegisterRoutes(clientCtx client.Context, r *mux.Router) {
// TransferTxReq defines the properties of a transfer tx request's body.
type TransferTxReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Amount sdk.Coins `json:"amount" yaml:"amount"`
Amount sdk.Coin `json:"amount" yaml:"amount"`
Receiver string `json:"receiver" yaml:"receiver"`
TimeoutHeight uint64 `json:"timeout_height" yaml:"timeout_height"`
TimeoutTimestamp uint64 `json:"timeout_timestamp" yaml:"timeout_timestamp"`

View File

@ -24,12 +24,12 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
// See createOutgoingPacket in spec:https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
func handleMsgTransfer(ctx sdk.Context, k keeper.Keeper, msg *types.MsgTransfer) (*sdk.Result, error) {
if err := k.SendTransfer(
ctx, msg.SourcePort, msg.SourceChannel, msg.Amount, msg.Sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp,
ctx, msg.SourcePort, msg.SourceChannel, msg.Token, msg.Sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp,
); err != nil {
return nil, err
}
k.Logger(ctx).Info("IBC transfer: %s from %s to %s", msg.Amount, msg.Sender, msg.Receiver)
k.Logger(ctx).Info("IBC transfer: %s from %s to %s", msg.Token, msg.Sender, msg.Receiver)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(

View File

@ -1,14 +1,13 @@
package transfer_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
ibctransfer "github.com/cosmos/cosmos-sdk/x/ibc-transfer"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing"
)
@ -29,41 +28,54 @@ func (suite *HandlerTestSuite) SetupTest() {
}
// constructs a send from chainA to chainB on the established channel/connection
// and sends the coins back from chainB to chainA.
// FIX: this test currently passes because source is incorrectly determined
// by the ibc-transfer module, so what actually occurs is chainA and chainB
// send coins to each other, but no coins are ever sent back. This can be
// fixed by receving and acknowledeging the send on the counterparty chain.
// https://github.com/cosmos/cosmos-sdk/issues/6827
// and sends the same coin back from chainB to chainA.
func (suite *HandlerTestSuite) TestHandleMsgTransfer() {
clientA, clientB, _, _, channelA, channelB := suite.coordinator.Setup(suite.chainA, suite.chainB)
handlerA := ibctransfer.NewHandler(suite.chainA.App.TransferKeeper)
originalBalance := suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)
coinToSendToB := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%s/%s/%s", channelB.PortID, channelB.ID, sdk.DefaultBondDenom), sdk.NewInt(100)))
coinToSendToB := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))
// send from chainA to chainB
msg := types.NewMsgTransfer(channelA.PortID, channelA.ID, coinToSendToB, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), 110, 0)
res, err := handlerA(suite.chainA.GetContext(), msg)
suite.Require().NoError(err)
suite.Require().NotNil(res, "%+v", res) // successfully executed
err = suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
err := suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
suite.Require().NoError(err) // message committed
handlerB := ibctransfer.NewHandler(suite.chainB.App.TransferKeeper)
// relay send
fungibleTokenPacket := types.NewFungibleTokenPacketData(coinToSendToB.Denom, coinToSendToB.Amount.Uint64(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String())
packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, 110, 0)
ack := types.FungibleTokenPacketAcknowledgement{Success: true}
err = suite.coordinator.RelayPacket(suite.chainA, suite.chainB, clientA, clientB, packet, ack.GetBytes())
suite.Require().NoError(err) // relay committed
coinToSendBackToA := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%s/%s/%s", channelA.PortID, channelA.ID, sdk.DefaultBondDenom), sdk.NewInt(100)))
// check that voucher exists on chain
voucherDenom := types.GetPrefixedDenom(packet.GetDestPort(), packet.GetDestChannel(), sdk.DefaultBondDenom)
balance := suite.chainB.App.BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), voucherDenom)
coinToSendBackToA := types.GetTransferCoin(channelB.PortID, channelB.ID, sdk.DefaultBondDenom, 100)
suite.Require().Equal(coinToSendBackToA, balance)
// send from chainB back to chainA
msg = types.NewMsgTransfer(channelA.PortID, channelA.ID, coinToSendBackToA, suite.chainB.SenderAccount.GetAddress(), suite.chainA.SenderAccount.GetAddress().String(), 110, 0)
res, err = handlerB(suite.chainB.GetContext(), msg)
suite.Require().NoError(err)
suite.Require().NotNil(res, "%+v", res) // successfully executed
msg = types.NewMsgTransfer(channelB.PortID, channelB.ID, coinToSendBackToA, suite.chainB.SenderAccount.GetAddress(), suite.chainA.SenderAccount.GetAddress().String(), 110, 0)
err = suite.coordinator.SendMsgs(suite.chainB, suite.chainA, clientA, msg)
suite.Require().NoError(err) // message committed
// relay send
fungibleTokenPacket = types.NewFungibleTokenPacketData(coinToSendBackToA.Denom, coinToSendBackToA.Amount.Uint64(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())
packet = channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelB.PortID, channelB.ID, channelA.PortID, channelA.ID, 110, 0)
err = suite.coordinator.RelayPacket(suite.chainB, suite.chainA, clientB, clientA, packet, ack.GetBytes())
suite.Require().NoError(err) // relay committed
balance = suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)
// check that the balance on chainA returned back to the original state
suite.Require().Equal(originalBalance, balance)
// check that module account escrow address is empty
escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
balance = suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom)
suite.Require().Equal(sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), balance)
}
func TestHandlerTestSuite(t *testing.T) {

View File

@ -1,8 +1,6 @@
package keeper
import (
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"
@ -12,19 +10,42 @@ import (
// SendTransfer handles transfer sending logic. There are 2 possible cases:
//
// 1. Sender chain is the source chain of the coins (i.e where they were minted): the coins
// are transferred to an escrow address (i.e locked) on the sender chain and then
// transferred to the destination chain (i.e not the source chain) via a packet
// with the corresponding fungible token data.
// 1. Sender chain is acting as the source zone. The coins are transferred
// to an escrow address (i.e locked) on the sender chain and then transferred
// to the receiving chain through IBC TAO logic. It is expected that the
// receiving chain will mint vouchers to the receiving address.
//
// 2. Coins are not native from the sender chain (i.e tokens sent where transferred over
// through IBC already): the coins are burned and then a packet is sent to the
// source chain of the tokens.
// 2. Sender chain is acting as the sink zone. The coins (vouchers) are burned
// on the sender chain and then transferred to the receiving chain though IBC
// TAO logic. It is expected that the receiving chain, which had previously
// sent the original denomination, will unescrow the fungible token and send
// it to the receiving address.
//
// Another way of thinking of source and sink zones is through the token's
// timeline. Each send to any chain other than the one it was previously
// received from is a movement forwards in the token's timeline. This causes
// trace to be added to the token's history and the destination port and
// destination channel to be prefixed to the denomination. In these instances
// the sender chain is acting as the source zone. When the token is sent back
// to the chain it previously received from, the prefix is removed. This is
// a backwards movement in the token's timeline and the sender chain
// is acting as the sink zone.
//
// Example:
// These steps of transfer occur: A -> B -> C -> A -> C -> B -> A
//
// 1. A -> B : sender chain is source zone. Denom upon receiving: 'B/denom'
// 2. B -> C : sender chain is source zone. Denom upon receiving: 'C/B/denom'
// 3. C -> A : sender chain is source zone. Denom upon receiving: 'A/C/B/denom'
// 4. A -> C : sender chain is sink zone. Denom upon receiving: 'C/B/denom'
// 5. C -> B : sender chain is sink zone. Denom upon receiving: 'B/denom'
// 6. B -> A : sender chain is sink zone. Denom upon receiving: 'denom'
func (k Keeper) SendTransfer(
ctx sdk.Context,
sourcePort,
sourceChannel string,
amount sdk.Coins,
token sdk.Coin,
sender sdk.AccAddress,
receiver string,
timeoutHeight,
@ -47,81 +68,39 @@ func (k Keeper) SendTransfer(
)
}
return k.createOutgoingPacket(
ctx, sequence, sourcePort, sourceChannel, destinationPort, destinationChannel,
amount, sender, receiver, timeoutHeight, timeoutTimestamp,
)
}
// begin createOutgoingPacket logic
// See spec for this logic: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
// See spec for this function: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
func (k Keeper) createOutgoingPacket(
ctx sdk.Context,
seq uint64,
sourcePort, sourceChannel,
destinationPort, destinationChannel string,
amount sdk.Coins,
sender sdk.AccAddress,
receiver string,
timeoutHeight, timeoutTimestamp uint64,
) error {
channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel))
if !ok {
return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability")
}
// NOTE:
// - Coins transferred from the destination chain should have their denomination
// prefixed with source port and channel IDs.
// - Coins transferred from the source chain can have their denomination
// clear from prefixes when transferred to the escrow account (i.e when they are
// locked) BUT MUST have the destination port and channel ID when constructing
// the packet data.
if len(amount) != 1 {
return sdkerrors.Wrapf(types.ErrOnlyOneDenomAllowed, "%d denoms included", len(amount))
}
prefix := types.GetDenomPrefix(destinationPort, destinationChannel)
source := strings.HasPrefix(amount[0].Denom, prefix)
// NOTE: SendTransfer simply sends the denomination as it exists on its own
// chain inside the packet data. The receiving chain will perform denom
// prefixing as necessary.
if source {
// clear the denomination from the prefix to send the coins to the escrow account
coins := make(sdk.Coins, len(amount))
for i, coin := range amount {
if strings.HasPrefix(coin.Denom, prefix) {
coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount)
} else {
coins[i] = coin
}
}
// escrow tokens if the destination chain is the same as the sender's
if types.SenderChainIsSource(sourcePort, sourceChannel, token.Denom) {
// create the escrow address for the tokens
escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel)
// escrow source tokens. It fails if balance insufficient.
if err := k.bankKeeper.SendCoins(
ctx, sender, escrowAddress, coins,
ctx, sender, escrowAddress, sdk.NewCoins(token),
); err != nil {
return err
}
} else {
// build the receiving denomination prefix if it's not present
prefix = types.GetDenomPrefix(sourcePort, sourceChannel)
for _, coin := range amount {
if !strings.HasPrefix(coin.Denom, prefix) {
return sdkerrors.Wrapf(types.ErrInvalidDenomForTransfer, "denom was: %s", coin.Denom)
}
}
// transfer the coins to the module account and burn them
if err := k.bankKeeper.SendCoinsFromAccountToModule(
ctx, sender, types.ModuleName, amount,
ctx, sender, types.ModuleName, sdk.NewCoins(token),
); err != nil {
return err
}
// burn vouchers from the sender's balance if the source is from another chain
if err := k.bankKeeper.BurnCoins(
ctx, types.ModuleName, amount,
ctx, types.ModuleName, sdk.NewCoins(token),
); err != nil {
// NOTE: should not happen as the module account was
// retrieved on the step above and it has enough balace
@ -131,12 +110,12 @@ func (k Keeper) createOutgoingPacket(
}
packetData := types.NewFungibleTokenPacketData(
amount, sender.String(), receiver,
token.Denom, token.Amount.Uint64(), sender.String(), receiver,
)
packet := channeltypes.NewPacket(
packetData.GetBytes(),
seq,
sequence,
sourcePort,
sourceChannel,
destinationPort,
@ -148,76 +127,87 @@ func (k Keeper) createOutgoingPacket(
return k.channelKeeper.SendPacket(ctx, channelCap, packet)
}
// OnRecvPacket processes a cross chain fungible token transfer. If the
// sender chain is the source of minted tokens then vouchers will be minted
// and sent to the receiving address. Otherwise if the sender chain is sending
// back tokens this chain originally transferred to it, the tokens are
// unescrowed and sent to the receiving address.
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error {
// NOTE: packet data type already checked in handler.go
if len(data.Amount) != 1 {
return sdkerrors.Wrapf(types.ErrOnlyOneDenomAllowed, "%d denoms included", len(data.Amount))
}
prefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
source := strings.HasPrefix(data.Amount[0].Denom, prefix)
// decode the receiver address
receiver, err := sdk.AccAddressFromBech32(data.Receiver)
if err != nil {
return err
}
if source {
// This is the prefix that would have been prefixed to the denomination
// on sender chain IF and only if the token originally came from the
// receiving chain.
//
// NOTE: We use SourcePort and SourceChannel here, because the counterparty
// chain would have prefixed with DestPort and DestChannel when originally
// receiving this coin as seen in the "sender chain is the source" condition.
// mint new tokens if the source of the transfer is the same chain
if err := k.bankKeeper.MintCoins(
ctx, types.ModuleName, data.Amount,
); err != nil {
return err
}
if types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) {
// sender chain is not the source, unescrow tokens
// send to receiver
return k.bankKeeper.SendCoinsFromModuleToAccount(
ctx, types.ModuleName, receiver, data.Amount,
)
// remove prefix added by sender chain
voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
unprefixedDenom := data.Denom[len(voucherPrefix):]
token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount))
// unescrow tokens
escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
return k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token))
}
// check the denom prefix
prefix = types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
coins := make(sdk.Coins, len(data.Amount))
for i, coin := range data.Amount {
if !strings.HasPrefix(coin.Denom, prefix) {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidCoins,
"%s doesn't contain the prefix '%s'", coin.Denom, prefix,
)
}
coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount)
// sender chain is the source, mint vouchers
// since SendPacket did not prefix the denomination, we must prefix denomination here
sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
// NOTE: sourcePrefix contains the trailing "/"
prefixedDenom := sourcePrefix + data.Denom
voucher := sdk.NewCoin(prefixedDenom, sdk.NewIntFromUint64(data.Amount))
// mint new tokens if the source of the transfer is the same chain
if err := k.bankKeeper.MintCoins(
ctx, types.ModuleName, sdk.NewCoins(voucher),
); err != nil {
return err
}
// unescrow tokens
escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
return k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, coins)
// send to receiver
return k.bankKeeper.SendCoinsFromModuleToAccount(
ctx, types.ModuleName, receiver, sdk.NewCoins(voucher),
)
}
// OnAcknowledgementPacket responds to the the success or failure of a packet
// acknowledgement written on the receiving chain. If the acknowledgement
// was a success then nothing occurs. If the acknowledgement failed, then
// the sender is refunded their tokens using the refundPacketToken function.
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData, ack types.FungibleTokenPacketAcknowledgement) error {
if !ack.Success {
return k.refundPacketAmount(ctx, packet, data)
return k.refundPacketToken(ctx, packet, data)
}
return nil
}
// OnTimeoutPacket refunds the sender since the original packet sent was
// never received and has been timed out.
func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error {
return k.refundPacketAmount(ctx, packet, data)
return k.refundPacketToken(ctx, packet, data)
}
func (k Keeper) refundPacketAmount(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error {
// refundPacketToken will unescrow and send back the tokens back to sender
// if the sending chain was the source chain. Otherwise, the sent tokens
// were burnt in the original send so new tokens are minted and sent to
// the sending address.
func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error {
// NOTE: packet data type already checked in handler.go
if len(data.Amount) != 1 {
return sdkerrors.Wrapf(types.ErrOnlyOneDenomAllowed, "%d denoms included", len(data.Amount))
}
// check the denom prefix
prefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
source := strings.HasPrefix(data.Amount[0].Denom, prefix)
token := sdk.NewCoin(data.Denom, sdk.NewIntFromUint64(data.Amount))
// decode the sender address
sender, err := sdk.AccAddressFromBech32(data.Sender)
@ -225,27 +215,18 @@ func (k Keeper) refundPacketAmount(ctx sdk.Context, packet channeltypes.Packet,
return err
}
if source {
coins := make(sdk.Coins, len(data.Amount))
for i, coin := range data.Amount {
coin := coin
if !strings.HasPrefix(coin.Denom, prefix) {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "%s doesn't contain the prefix '%s'", coin.Denom, prefix)
}
coins[i] = sdk.NewCoin(coin.Denom[len(prefix):], coin.Amount)
}
if types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), token.Denom) {
// unescrow tokens back to sender
escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel())
return k.bankKeeper.SendCoins(ctx, escrowAddress, sender, coins)
return k.bankKeeper.SendCoins(ctx, escrowAddress, sender, sdk.NewCoins(token))
}
// mint vouchers back to sender
if err := k.bankKeeper.MintCoins(
ctx, types.ModuleName, data.Amount,
ctx, types.ModuleName, sdk.NewCoins(token),
); err != nil {
return err
}
return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, data.Amount)
return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, sdk.NewCoins(token))
}

View File

@ -35,20 +35,20 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
{"successful transfer from source chain",
func() {
_, _, _, _, channelA, channelB = suite.coordinator.Setup(suite.chainA, suite.chainB)
amount = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
amount = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, true},
{"successful transfer with coins from counterparty chain",
func() {
// send coins from chainA back to chainB
_, _, _, _, channelA, channelB = suite.coordinator.Setup(suite.chainA, suite.chainB)
amount = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
amount = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
}, false, true},
{"source channel not found",
func() {
// channel references wrong ID
_, _, _, _, channelA, channelB = suite.coordinator.Setup(suite.chainA, suite.chainB)
channelA.ID = ibctesting.InvalidID
amount = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
amount = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, false},
{"next seq send not found",
func() {
@ -62,7 +62,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
channeltypes.NewChannel(channeltypes.OPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(channelB.PortID, channelB.ID), []string{connA.ID}, ibctesting.ChannelVersion),
)
suite.chainA.CreateChannelCapability(channelA.PortID, channelA.ID)
amount = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
amount = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, false},
// createOutgoingPacket tests
@ -70,13 +70,13 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
{"send coins failed",
func() {
_, _, _, _, channelA, channelB = suite.coordinator.Setup(suite.chainA, suite.chainB)
amount = ibctesting.NewTransferCoins(channelB, "randomdenom", 100)
amount = types.GetTransferCoin(channelB, "randomdenom", 100)
}, true, false},
// - receiving chain
{"send from module account failed",
func() {
_, _, _, _, channelA, channelB = suite.coordinator.Setup(suite.chainA, suite.chainB)
amount = ibctesting.NewTransferCoins(channelA, "randomdenom", 100)
amount = types.GetTransferCoin(channelA, "randomdenom", 100)
}, false, false},
{"channel capability not found",
func() {
@ -98,7 +98,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() {
if !tc.source {
// send coins from chainB to chainA
coinFromBToA := ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
coinFromBToA := types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
transferMsg := types.NewMsgTransfer(channelB.PortID, channelB.ID, coinFromBToA, suite.chainB.SenderAccount.GetAddress(), suite.chainA.SenderAccount.GetAddress().String(), 110, 0)
err = suite.coordinator.SendMsgs(suite.chainB, suite.chainA, channelA.ClientID, transferMsg)
suite.Require().NoError(err) // message committed
@ -163,12 +163,12 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
// onRecvPacket
// - coins from source chain (chainA)
{"failure: mint zero coins", func() {
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 0)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 0)
}, true, false},
// - coins being sent back to original chain (chainB)
{"tries to unescrow more tokens than allowed", func() {
coins = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 1000000)
coins = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 1000000)
}, false, false},
}
@ -184,38 +184,23 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
if !tc.source {
// send coins from chainB to chainA, receive them, acknowledge them, and send back to chainB
coinFromBToA := ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
coinFromBToA := types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
transferMsg := types.NewMsgTransfer(channelB.PortID, channelB.ID, coinFromBToA, suite.chainB.SenderAccount.GetAddress(), suite.chainA.SenderAccount.GetAddress().String(), 110, 0)
err := suite.coordinator.SendMsgs(suite.chainB, suite.chainA, channelA.ClientID, transferMsg)
suite.Require().NoError(err) // message committed
// receive coins on chainA from chainB
// relay send packet
fungibleTokenPacket := types.NewFungibleTokenPacketData(coinFromBToA, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())
packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelB.PortID, channelB.ID, channelA.PortID, channelA.ID, 110, 0)
// get proof of packet commitment from chainB
packetKey := host.KeyPacketCommitment(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
proof, proofHeight := suite.chainB.QueryProof(packetKey)
recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, suite.chainA.SenderAccount.GetAddress())
err = suite.coordinator.SendMsgs(suite.chainA, suite.chainB, channelB.ClientID, recvMsg)
suite.Require().NoError(err) // message committed
// get proof of acknowledgement on chainA
packetKey = host.KeyPacketAcknowledgement(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
proof, proofHeight = suite.chainA.QueryProof(packetKey)
// acknowledge on chainB the receive that happened on chainA
ack := types.FungibleTokenPacketAcknowledgement{true, ""}
ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack.GetBytes(), proof, proofHeight, suite.chainB.SenderAccount.GetAddress())
err = suite.coordinator.SendMsgs(suite.chainB, suite.chainA, channelA.ClientID, ackMsg)
suite.Require().NoError(err) // message committed
ack := types.FungibleTokenPacketAcknowledgement{Success: true}
err = suite.coordinator.RelayPacket(suite.chainB, suite.chainA, clientB, clientA, packet, ack.GetBytes())
suite.Require().NoError(err) // relay committed
seq++
// NOTE: coins must be explicitly changed in malleate to test invalid cases
coins = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
} else {
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}
// send coins from chainA to chainB
@ -264,7 +249,7 @@ func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() {
success bool // success of ack
}{
{"success ack causes no-op", successAck, func() {
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, true},
{"successful refund from source chain", failedAck,
func() {
@ -272,11 +257,11 @@ func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() {
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), escrow, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))))
suite.Require().NoError(err)
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, false},
{"successful refund from external chain", failedAck,
func() {
coins = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
}, false, false},
}
@ -339,11 +324,11 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacket() {
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), escrow, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))))
suite.Require().NoError(err)
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, true},
{"successful timeout from external chain",
func() {
coins = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 100)
}, false, true},
{"no source prefix on coin denom",
func() {
@ -351,11 +336,11 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacket() {
}, false, false},
{"unescrow failed",
func() {
coins = ibctesting.NewTransferCoins(channelB, sdk.DefaultBondDenom, 100)
coins = types.GetTransferCoin(channelB, sdk.DefaultBondDenom, 100)
}, true, false},
{"mint failed",
func() {
coins = ibctesting.NewTransferCoins(channelA, sdk.DefaultBondDenom, 0)
coins = types.GetTransferCoin(channelA, sdk.DefaultBondDenom, 0)
}, true, false},
}

View File

@ -306,7 +306,8 @@ func (am AppModule) OnRecvPacket(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyValue, data.Amount.String()),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, fmt.Sprintf("%d", data.Amount)),
),
)
@ -338,7 +339,8 @@ func (am AppModule) OnAcknowledgementPacket(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyValue, data.Amount.String()),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, fmt.Sprintf("%d", data.Amount)),
sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success)),
),
)
@ -375,7 +377,8 @@ func (am AppModule) OnTimeoutPacket(
types.EventTypeTimeout,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender),
sdk.NewAttribute(types.AttributeKeyRefundValue, data.Amount.String()),
sdk.NewAttribute(types.AttributeKeyRefundDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyRefundAmount, fmt.Sprintf("%d", data.Amount)),
),
)

View File

@ -3,3 +3,38 @@ order: 4
-->
# Messages
### MsgTransfer
A fungible token cross chain transfer is achieved by using the `MsgTransfer`:
```go
type MsgTransfer struct {
SourcePort string
SourceChannel string
Token sdk.Coin
Sender sdk.AccAddress
Receiver string
TimeoutHeight uint64
TimeoutTimestamp uint64
}
```
This message is expected to fail if:
- `SourcePort` is invalid (see 24-host naming requirements)
- `SourceChannel` is invalid (see 24-host naming requirements)
- `Token` is invalid (denom is invalid or amount is negative)
- `Token.Amount` is not positive
- `Sender` is empty
- `Receiver` is empty
- `TimeoutHeight` and `TimeoutTimestamp` are both zero
This message will send a fungible token to the counterparty chain represented
by the counterparty Channel End connected to the Channel End with the identifiers
`SourcePort` and `SourceChannel`.
The denomination provided for transfer should correspond to the same denomination
represented on this chain. The prefixes will be added as necessary upon by the
receiving chain.

View File

@ -19,7 +19,8 @@ order: 5
|-----------------------|---------------|-----------------|
| fungible_token_packet | module | transfer |
| fungible_token_packet | receiver | {receiver} |
| fungible_token_packet | value | {amount} |
| fungible_token_packet | denom | {denom} |
| fungible_token_packet | amount | {amount} |
## OnAcknowledgePacket callback
@ -27,7 +28,8 @@ order: 5
|-----------------------|---------------|-----------------|
| fungible_token_packet | module | transfer |
| fungible_token_packet | receiver | {receiver} |
| fungible_token_packet | value | {amount} |
| fungible_token_packet | denom | {denom} |
| fungible_token_packet | amount | {amount} |
| fungible_token_packet | success | {ackSuccess} |
## OnTimeoutPacket callback
@ -36,8 +38,8 @@ order: 5
|-----------------------|-----------------|-----------------|
| fungible_token_packet | module | transfer |
| fungible_token_packet | refund_receiver | {receiver} |
| fungible_token_packet | value | {amount} |
| fungible_token_packet | denom | {denom} |
| fungible_token_packet | amount | {amount} |

View File

@ -0,0 +1,52 @@
package types
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// SenderChainIsSource returns false if the denomination originally came
// from the receiving chain and true otherwise.
func SenderChainIsSource(sourcePort, sourceChannel, denom string) bool {
// This is the prefix that would have been prefixed to the denomination
// on sender chain IF and only if the token originally came from the
// receiving chain.
return !ReceiverChainIsSource(sourcePort, sourceChannel, denom)
}
// ReceiverChainIsSource returns true if the denomination originally came
// from the receiving chain and false otherwise.
func ReceiverChainIsSource(sourcePort, sourceChannel, denom string) bool {
// The prefix passed in should contain the SourcePort and SourceChannel.
// If the receiver chain originally sent the token to the sender chain
// the denom will have the sender's SourcePort and SourceChannel as the
// prefix.
voucherPrefix := GetDenomPrefix(sourcePort, sourceChannel)
return strings.HasPrefix(denom, voucherPrefix)
}
// GetDenomPrefix returns the receiving denomination prefix
func GetDenomPrefix(portID, channelID string) string {
return fmt.Sprintf("%s/%s/", portID, channelID)
}
// GetPrefixedDenom returns the denomination with the portID and channelID prefixed
func GetPrefixedDenom(portID, channelID, baseDenom string) string {
return fmt.Sprintf("%s/%s/%s", portID, channelID, baseDenom)
}
// GetPrefixedCoin creates a copy of the given coin with the prefixed denom
func GetPrefixedCoin(portID, channelID string, coin sdk.Coin) sdk.Coin {
return sdk.NewCoin(GetPrefixedDenom(portID, channelID, coin.Denom), coin.Amount)
}
// GetTransferCoin creates a transfer coin with the port ID and channel ID
// prefixed to the base denom.
func GetTransferCoin(portID, channelID, baseDenom string, amount int64) sdk.Coin {
return sdk.NewInt64Coin(GetPrefixedDenom(portID, channelID, baseDenom), amount)
}

View File

@ -7,7 +7,7 @@ import (
// IBC channel sentinel errors
var (
ErrInvalidPacketTimeout = sdkerrors.Register(ModuleName, 2, "invalid packet timeout")
ErrOnlyOneDenomAllowed = sdkerrors.Register(ModuleName, 3, "only one denom allowed")
ErrInvalidDenomForTransfer = sdkerrors.Register(ModuleName, 4, "invalid denomination for cross-chain transfer")
ErrInvalidVersion = sdkerrors.Register(ModuleName, 5, "invalid ICS20 version")
ErrInvalidDenomForTransfer = sdkerrors.Register(ModuleName, 3, "invalid denomination for cross-chain transfer")
ErrInvalidVersion = sdkerrors.Register(ModuleName, 4, "invalid ICS20 version")
ErrInvalidAmount = sdkerrors.Register(ModuleName, 5, "invalid token amount")
)

View File

@ -8,9 +8,11 @@ const (
EventTypeChannelClose = "channel_closed"
AttributeKeyReceiver = "receiver"
AttributeKeyValue = "value"
AttributeKeyDenom = "denom"
AttributeKeyAmount = "amount"
AttributeKeyRefundReceiver = "refund_receiver"
AttributeKeyRefundValue = "refund_value"
AttributeKeyRefundDenom = "refund_denom"
AttributeKeyRefundAmount = "refund_amount"
AttributeKeyAckSuccess = "success"
AttributeKeyAckError = "error"
)

View File

@ -1,8 +1,6 @@
package types
import (
"fmt"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -40,17 +38,3 @@ var PortKey = []byte{0x01}
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)
}
// GetPrefixedCoins creates a copy of the given coins with the denom updated with the prefix.
func GetPrefixedCoins(portID, channelID string, coins ...sdk.Coin) sdk.Coins {
prefixedCoins := make(sdk.Coins, len(coins))
for i := range coins {
prefixedCoins[i] = sdk.NewCoin(GetDenomPrefix(portID, channelID)+coins[i].Denom, coins[i].Amount)
}
return prefixedCoins
}

View File

@ -14,13 +14,13 @@ const (
// NewMsgTransfer creates a new MsgTransfer instance
func NewMsgTransfer(
sourcePort, sourceChannel string,
amount sdk.Coins, sender sdk.AccAddress, receiver string,
token sdk.Coin, sender sdk.AccAddress, receiver string,
timeoutHeight, timeoutTimestamp uint64,
) *MsgTransfer {
return &MsgTransfer{
SourcePort: sourcePort,
SourceChannel: sourceChannel,
Amount: amount,
Token: token,
Sender: sender,
Receiver: receiver,
TimeoutHeight: timeoutHeight,
@ -39,7 +39,7 @@ func (MsgTransfer) Type() string {
}
// ValidateBasic performs a basic check of the MsgTransfer fields.
// NOTE: timeout height and timestamp values can be 0 to disable the timeout.
// NOTE: timeout height or timestamp values can be 0 to disable the timeout.
func (msg MsgTransfer) ValidateBasic() error {
if err := host.PortIdentifierValidator(msg.SourcePort); err != nil {
return sdkerrors.Wrap(err, "invalid source port ID")
@ -47,11 +47,11 @@ func (msg MsgTransfer) ValidateBasic() error {
if err := host.ChannelIdentifierValidator(msg.SourceChannel); err != nil {
return sdkerrors.Wrap(err, "invalid source channel ID")
}
if !msg.Amount.IsAllPositive() {
return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, msg.Amount.String())
if !msg.Token.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Token.String())
}
if !msg.Amount.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String())
if !msg.Token.IsPositive() {
return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, msg.Token.String())
}
if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing sender address")
@ -59,7 +59,12 @@ func (msg MsgTransfer) ValidateBasic() error {
if msg.Receiver == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing recipient address")
}
return nil
// sanity check that validate basic on fungible token packet passes
// NOTE: this should always pass since validation checks should be the
// same. Please open an issue if you encounter an error on this line.
packet := NewFungibleTokenPacketData(msg.Token.Denom, msg.Token.Amount.Uint64(), msg.Sender.String(), msg.Receiver)
return packet.ValidateBasic()
}
// GetSignBytes implements sdk.Msg

View File

@ -27,82 +27,68 @@ var (
addr2 = sdk.AccAddress("testaddr2").String()
emptyAddr sdk.AccAddress
coins, _ = sdk.ParseCoins("100atom")
invalidDenomCoins = sdk.Coins{sdk.Coin{Denom: "ato-m", Amount: sdk.NewInt(100)}}
negativeCoins = sdk.Coins{sdk.Coin{Denom: "atom", Amount: sdk.NewInt(100)}, sdk.Coin{Denom: "atoms", Amount: sdk.NewInt(-100)}}
coin = sdk.NewCoin("atom", sdk.NewInt(100))
invalidDenomCoin = sdk.Coin{Denom: "ato-m", Amount: sdk.NewInt(100)}
negativeCoin = sdk.Coin{Denom: "atoms", Amount: sdk.NewInt(-100)}
)
// TestMsgTransferRoute tests Route for MsgTransfer
func TestMsgTransferRoute(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coins, addr1, addr2, 10, 0)
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0)
require.Equal(t, RouterKey, msg.Route())
}
// TestMsgTransferType tests Type for MsgTransfer
func TestMsgTransferType(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coins, addr1, addr2, 10, 0)
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0)
require.Equal(t, "transfer", msg.Type())
}
// TestMsgTransferValidation tests ValidateBasic for MsgTransfer
func TestMsgTransferValidation(t *testing.T) {
testMsgs := []*MsgTransfer{
NewMsgTransfer(validPort, validChannel, coins, addr1, addr2, 10, 0), // valid msg
NewMsgTransfer(invalidShortPort, validChannel, coins, addr1, addr2, 10, 0), // too short port id
NewMsgTransfer(invalidLongPort, validChannel, coins, addr1, addr2, 10, 0), // too long port id
NewMsgTransfer(invalidPort, validChannel, coins, addr1, addr2, 10, 0), // port id contains non-alpha
NewMsgTransfer(validPort, invalidShortChannel, coins, addr1, addr2, 10, 0), // too short channel id
NewMsgTransfer(validPort, invalidLongChannel, coins, addr1, addr2, 10, 0), // too long channel id
NewMsgTransfer(validPort, invalidChannel, coins, addr1, addr2, 10, 0), // channel id contains non-alpha
NewMsgTransfer(validPort, validChannel, invalidDenomCoins, addr1, addr2, 10, 0), // invalid amount
NewMsgTransfer(validPort, validChannel, negativeCoins, addr1, addr2, 10, 0), // amount contains negative coin
NewMsgTransfer(validPort, validChannel, coins, emptyAddr, addr2, 10, 0), // missing sender address
NewMsgTransfer(validPort, validChannel, coins, addr1, "", 10, 0), // missing recipient address
NewMsgTransfer(validPort, validChannel, sdk.Coins{}, addr1, addr2, 10, 0), // not possitive coin
}
testCases := []struct {
name string
msg *MsgTransfer
expPass bool
errMsg string
}{
{testMsgs[0], true, ""},
{testMsgs[1], false, "too short port id"},
{testMsgs[2], false, "too long port id"},
{testMsgs[3], false, "port id contains non-alpha"},
{testMsgs[4], false, "too short channel id"},
{testMsgs[5], false, "too long channel id"},
{testMsgs[6], false, "channel id contains non-alpha"},
{testMsgs[7], false, "invalid amount"},
{testMsgs[8], false, "amount contains negative coin"},
{testMsgs[9], false, "missing sender address"},
{testMsgs[10], false, "missing recipient address"},
{"valid msg", NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0), true},
{"too short port id", NewMsgTransfer(invalidShortPort, validChannel, coin, addr1, addr2, 10, 0), false},
{"too long port id", NewMsgTransfer(invalidLongPort, validChannel, coin, addr1, addr2, 10, 0), false},
{"port id contains non-alpha", NewMsgTransfer(invalidPort, validChannel, coin, addr1, addr2, 10, 0), false},
{"too short channel id", NewMsgTransfer(validPort, invalidShortChannel, coin, addr1, addr2, 10, 0), false},
{"too long channel id", NewMsgTransfer(validPort, invalidLongChannel, coin, addr1, addr2, 10, 0), false},
{"channel id contains non-alpha", NewMsgTransfer(validPort, invalidChannel, coin, addr1, addr2, 10, 0), false},
{"invalid amount", NewMsgTransfer(validPort, validChannel, invalidDenomCoin, addr1, addr2, 10, 0), false},
{"negative coin", NewMsgTransfer(validPort, validChannel, negativeCoin, addr1, addr2, 10, 0), false},
{"missing sender address", NewMsgTransfer(validPort, validChannel, coin, emptyAddr, addr2, 10, 0), false},
{"missing recipient address", NewMsgTransfer(validPort, validChannel, coin, addr1, "", 10, 0), false},
{"empty coin", NewMsgTransfer(validPort, validChannel, sdk.Coin{}, addr1, addr2, 10, 0), false},
}
for i, tc := range testCases {
err := tc.msg.ValidateBasic()
if tc.expPass {
require.NoError(t, err, "Msg %d failed: %v", i, err)
require.NoError(t, err, "valid test case %d failed: %v", i, err)
} else {
require.Error(t, err, "Invalid Msg %d passed: %s", i, tc.errMsg)
require.Error(t, err, "invalid test case %d passed: %s", i, tc.name)
}
}
}
// TestMsgTransferGetSignBytes tests GetSignBytes for MsgTransfer
func TestMsgTransferGetSignBytes(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coins, addr1, addr2, 110, 10)
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 110, 10)
res := msg.GetSignBytes()
expected := `{"type":"cosmos-sdk/MsgTransfer","value":{"amount":[{"amount":"100","denom":"atom"}],"receiver":"cosmos1w3jhxarpv3j8yvs7f9y7g","sender":"cosmos1w3jhxarpv3j8yvg4ufs4x","source_channel":"testchannel","source_port":"testportid","timeout_height":"110","timeout_timestamp":"10"}}`
expected := `{"type":"cosmos-sdk/MsgTransfer","value":{"receiver":"cosmos1w3jhxarpv3j8yvs7f9y7g","sender":"cosmos1w3jhxarpv3j8yvg4ufs4x","source_channel":"testchannel","source_port":"testportid","timeout_height":"110","timeout_timestamp":"10","token":{"amount":"100","denom":"atom"}}}`
require.Equal(t, expected, string(res))
}
// TestMsgTransferGetSigners tests GetSigners for MsgTransfer
func TestMsgTransferGetSigners(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coins, addr1, addr2, 10, 0)
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0)
res := msg.GetSigners()
expected := "[746573746164647231]"

View File

@ -23,8 +23,11 @@ var (
// NewFungibleTokenPacketData contructs a new FungibleTokenPacketData instance
func NewFungibleTokenPacketData(
amount sdk.Coins, sender, receiver string) FungibleTokenPacketData {
denom string, amount uint64,
sender, receiver string,
) FungibleTokenPacketData {
return FungibleTokenPacketData{
Denom: denom,
Amount: amount,
Sender: sender,
Receiver: receiver,
@ -33,11 +36,11 @@ func NewFungibleTokenPacketData(
// ValidateBasic is used for validating the token transfer
func (ftpd FungibleTokenPacketData) ValidateBasic() error {
if !ftpd.Amount.IsAllPositive() {
return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, ftpd.Amount.String())
if strings.TrimSpace(ftpd.Denom) == "" {
return sdkerrors.Wrap(ErrInvalidDenomForTransfer, "denom cannot be empty")
}
if !ftpd.Amount.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, ftpd.Amount.String())
if ftpd.Amount == 0 {
return sdkerrors.Wrap(ErrInvalidAmount, "amount cannot be 0")
}
if strings.TrimSpace(ftpd.Sender) == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be blank")

View File

@ -6,34 +6,31 @@ import (
"github.com/stretchr/testify/require"
)
const (
denom = "transfer/gaiachannel/atom"
amount = uint64(100)
)
// TestFungibleTokenPacketDataValidateBasic tests ValidateBasic for FungibleTokenPacketData
func TestFungibleTokenPacketDataValidateBasic(t *testing.T) {
testPacketDataTransfer := []FungibleTokenPacketData{
NewFungibleTokenPacketData(coins, addr1.String(), addr2), // valid msg
NewFungibleTokenPacketData(invalidDenomCoins, addr1.String(), addr2), // invalid amount
NewFungibleTokenPacketData(negativeCoins, addr1.String(), addr2), // amount contains negative coin
NewFungibleTokenPacketData(coins, emptyAddr.String(), addr2), // missing sender address
NewFungibleTokenPacketData(coins, addr1.String(), emptyAddr.String()), // missing recipient address
}
testCases := []struct {
name string
packetData FungibleTokenPacketData
expPass bool
errMsg string
}{
{testPacketDataTransfer[0], true, ""},
{testPacketDataTransfer[1], false, "invalid amount"},
{testPacketDataTransfer[2], false, "amount contains negative coin"},
{testPacketDataTransfer[3], false, "missing sender address"},
{testPacketDataTransfer[4], false, "missing recipient address"},
{"valid packet", NewFungibleTokenPacketData(denom, amount, addr1.String(), addr2), true},
{"invalid denom", NewFungibleTokenPacketData("", amount, addr1.String(), addr2), false},
{"invalid amount", NewFungibleTokenPacketData(denom, 0, addr1.String(), addr2), false},
{"missing sender address", NewFungibleTokenPacketData(denom, amount, emptyAddr.String(), addr2), false},
{"missing recipient address", NewFungibleTokenPacketData(denom, amount, addr1.String(), emptyAddr.String()), false},
}
for i, tc := range testCases {
err := tc.packetData.ValidateBasic()
if tc.expPass {
require.NoError(t, err, "PacketDataTransfer %d failed: %v", i, err)
require.NoError(t, err, "valid test case %d failed: %v", i, err)
} else {
require.Error(t, err, "Invalid PacketDataTransfer %d passed: %s", i, tc.errMsg)
require.Error(t, err, "invalid test case %d passed: %s", i, tc.name)
}
}
}

View File

@ -25,15 +25,16 @@ var _ = math.Inf
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between ICS20 enabled chains.
// See ICS Spec here: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between
// ICS20 enabled chains. See ICS Spec here:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
type MsgTransfer struct {
// the port on which the packet will be sent
SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty" yaml:"source_port"`
// the channel by which the packet will be sent
SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty" yaml:"source_channel"`
// the tokens to be transferred
Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,3,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"`
Token types.Coin `protobuf:"bytes,3,opt,name=token,proto3" json:"token"`
// the sender address
Sender github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,4,opt,name=sender,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"sender,omitempty"`
// the recipient address on the destination chain
@ -93,11 +94,11 @@ func (m *MsgTransfer) GetSourceChannel() string {
return ""
}
func (m *MsgTransfer) GetAmount() github_com_cosmos_cosmos_sdk_types.Coins {
func (m *MsgTransfer) GetToken() types.Coin {
if m != nil {
return m.Amount
return m.Token
}
return nil
return types.Coin{}
}
func (m *MsgTransfer) GetSender() github_com_cosmos_cosmos_sdk_types.AccAddress {
@ -129,14 +130,17 @@ func (m *MsgTransfer) GetTimeoutTimestamp() uint64 {
}
// FungibleTokenPacketData defines a struct for the packet payload
// See FungibleTokenPacketData spec: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
// See FungibleTokenPacketData spec:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#data-structures
type FungibleTokenPacketData struct {
// the tokens to be transferred
Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"`
// the token denomination to be transferred
Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"`
// the token amount to be transferred
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
// the sender address
Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"`
// the recipient address on the destination chain
Receiver string `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"`
}
func (m *FungibleTokenPacketData) Reset() { *m = FungibleTokenPacketData{} }
@ -172,11 +176,18 @@ func (m *FungibleTokenPacketData) XXX_DiscardUnknown() {
var xxx_messageInfo_FungibleTokenPacketData proto.InternalMessageInfo
func (m *FungibleTokenPacketData) GetAmount() github_com_cosmos_cosmos_sdk_types.Coins {
func (m *FungibleTokenPacketData) GetDenom() string {
if m != nil {
return m.Denom
}
return ""
}
func (m *FungibleTokenPacketData) GetAmount() uint64 {
if m != nil {
return m.Amount
}
return nil
return 0
}
func (m *FungibleTokenPacketData) GetSender() string {
@ -193,9 +204,10 @@ func (m *FungibleTokenPacketData) GetReceiver() string {
return ""
}
// FungibleTokenPacketAcknowledgement contains a boolean success flag and an optional error msg
// error msg is empty string on success
// See spec for onAcknowledgePacket: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
// FungibleTokenPacketAcknowledgement contains a boolean success flag and an
// optional error msg error msg is empty string on success See spec for
// onAcknowledgePacket:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
type FungibleTokenPacketAcknowledgement struct {
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
@ -257,38 +269,38 @@ func init() {
func init() { proto.RegisterFile("ibc/transfer/transfer.proto", fileDescriptor_08134a70fd29e656) }
var fileDescriptor_08134a70fd29e656 = []byte{
// 484 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0x3f, 0x6f, 0xd3, 0x40,
0x1c, 0x8d, 0x49, 0x9b, 0x96, 0x4b, 0x8a, 0xe0, 0x28, 0xe5, 0x08, 0xc8, 0x8e, 0x3c, 0x79, 0x49,
0x4c, 0x61, 0x40, 0x62, 0x22, 0x29, 0x42, 0x54, 0x08, 0xa9, 0xb2, 0x22, 0x06, 0x96, 0xca, 0x3e,
0xff, 0x70, 0x4e, 0x89, 0xef, 0xa2, 0xbb, 0x33, 0xd0, 0x6f, 0xc1, 0x97, 0x60, 0xe1, 0x93, 0x74,
0xec, 0xc0, 0xc0, 0x64, 0x50, 0xf2, 0x0d, 0x32, 0x32, 0x21, 0xff, 0x89, 0x89, 0xa5, 0x08, 0x31,
0x74, 0xba, 0x7b, 0xef, 0xfd, 0x7e, 0xcf, 0xf7, 0x7b, 0xbe, 0x43, 0x0f, 0x59, 0x40, 0x5d, 0x2d,
0x7d, 0xae, 0x3e, 0x80, 0xac, 0x36, 0x83, 0xb9, 0x14, 0x5a, 0xe0, 0x0e, 0x0b, 0xe8, 0x60, 0xcd,
0x75, 0x0f, 0x23, 0x11, 0x89, 0x5c, 0x70, 0xb3, 0x5d, 0x51, 0xd3, 0xbd, 0x4b, 0x85, 0x8a, 0x85,
0x72, 0x8b, 0xa5, 0x20, 0xed, 0xef, 0x4d, 0xd4, 0x7e, 0xab, 0xa2, 0x71, 0xd9, 0x8a, 0x9f, 0xa1,
0xb6, 0x12, 0x89, 0xa4, 0x70, 0x3e, 0x17, 0x52, 0x13, 0xa3, 0x67, 0x38, 0x37, 0x47, 0x47, 0xab,
0xd4, 0xc2, 0x17, 0x7e, 0x3c, 0x7b, 0x6e, 0x6f, 0x88, 0xb6, 0x87, 0x0a, 0x74, 0x26, 0xa4, 0xc6,
0x2f, 0xd0, 0xad, 0x52, 0xa3, 0x13, 0x9f, 0x73, 0x98, 0x91, 0x1b, 0x79, 0xef, 0x83, 0x55, 0x6a,
0xdd, 0xab, 0xf5, 0x96, 0xba, 0xed, 0x1d, 0x14, 0xc4, 0x49, 0x81, 0xf1, 0x3b, 0xd4, 0xf2, 0x63,
0x91, 0x70, 0x4d, 0x9a, 0xbd, 0xa6, 0xd3, 0x7e, 0xd2, 0x19, 0x94, 0x27, 0x3d, 0x11, 0x8c, 0x8f,
0x1e, 0x5f, 0xa6, 0x56, 0xe3, 0xdb, 0x4f, 0xcb, 0x89, 0x98, 0x9e, 0x24, 0xc1, 0x80, 0x8a, 0xd8,
0xad, 0x0d, 0xd4, 0x57, 0xe1, 0xd4, 0xd5, 0x17, 0x73, 0x28, 0x1a, 0x94, 0x57, 0xba, 0xe1, 0x53,
0xd4, 0x52, 0xc0, 0x43, 0x90, 0x64, 0xa7, 0x67, 0x38, 0x9d, 0xd1, 0xf1, 0xef, 0xd4, 0xea, 0xff,
0x87, 0xcb, 0x90, 0xd2, 0x61, 0x18, 0x4a, 0x50, 0xca, 0x2b, 0x0d, 0x70, 0x17, 0xed, 0x4b, 0xa0,
0xc0, 0x3e, 0x82, 0x24, 0xbb, 0xd9, 0x78, 0x5e, 0x85, 0xb3, 0x00, 0x34, 0x8b, 0x41, 0x24, 0xfa,
0x7c, 0x02, 0x2c, 0x9a, 0x68, 0xd2, 0xea, 0x19, 0xce, 0xce, 0x66, 0x00, 0x75, 0xdd, 0xf6, 0x0e,
0x4a, 0xe2, 0x75, 0x8e, 0xf1, 0x29, 0xba, 0xb3, 0xae, 0xc8, 0x56, 0xa5, 0xfd, 0x78, 0x4e, 0xf6,
0x72, 0x93, 0x47, 0xab, 0xd4, 0x22, 0x75, 0x93, 0xaa, 0xc4, 0xf6, 0x6e, 0x97, 0xdc, 0xb8, 0xa2,
0xbe, 0x1a, 0xe8, 0xfe, 0xab, 0x84, 0x47, 0x2c, 0x98, 0xc1, 0x58, 0x4c, 0x81, 0x9f, 0xf9, 0x74,
0x0a, 0xfa, 0xa5, 0xaf, 0xfd, 0x8d, 0x9c, 0x8d, 0x6b, 0xcd, 0xf9, 0xa8, 0xca, 0x39, 0xff, 0xf3,
0x5b, 0x43, 0x6b, 0xd6, 0x43, 0xb3, 0xc7, 0xc8, 0xde, 0x72, 0xcc, 0x21, 0x9d, 0x72, 0xf1, 0x69,
0x06, 0x61, 0x04, 0x31, 0x70, 0x8d, 0x09, 0xda, 0x53, 0x09, 0xa5, 0xa0, 0x54, 0x7e, 0x21, 0xf7,
0xbd, 0x35, 0xc4, 0x87, 0x68, 0x17, 0xa4, 0x14, 0xeb, 0x4f, 0x16, 0x60, 0xf4, 0xe6, 0x72, 0x61,
0x1a, 0x57, 0x0b, 0xd3, 0xf8, 0xb5, 0x30, 0x8d, 0x2f, 0x4b, 0xb3, 0x71, 0xb5, 0x34, 0x1b, 0x3f,
0x96, 0x66, 0xe3, 0xfd, 0xf1, 0x3f, 0xa7, 0xfa, 0xec, 0xb2, 0x80, 0xf6, 0xff, 0xbe, 0xb1, 0x6c,
0xc8, 0xa0, 0x95, 0x3f, 0x94, 0xa7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x95, 0x43, 0xad,
0x80, 0x03, 0x00, 0x00,
// 485 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0xcf, 0x6e, 0xd3, 0x30,
0x18, 0x6f, 0x68, 0xda, 0x0d, 0xb7, 0x43, 0x60, 0xc6, 0x08, 0x05, 0x25, 0x55, 0x4e, 0xb9, 0x34,
0xd1, 0xe0, 0x80, 0xc4, 0x89, 0x76, 0x08, 0x31, 0x21, 0xa4, 0xc9, 0xea, 0x89, 0xcb, 0x94, 0x38,
0x1f, 0x69, 0xd4, 0xc6, 0xae, 0x6c, 0x07, 0x98, 0x78, 0x09, 0x9e, 0x82, 0x67, 0xd9, 0x71, 0x47,
0x4e, 0x11, 0x6a, 0xdf, 0xa0, 0x47, 0x4e, 0x28, 0x71, 0x5a, 0x16, 0xa9, 0xda, 0xc9, 0xfe, 0xfd,
0xf9, 0xbe, 0x7c, 0x7f, 0x1c, 0xf4, 0x3c, 0x8d, 0x68, 0xa0, 0x44, 0xc8, 0xe4, 0x17, 0x10, 0xbb,
0x8b, 0xbf, 0x14, 0x5c, 0x71, 0xdc, 0x4f, 0x23, 0xea, 0x6f, 0xb9, 0xc1, 0x71, 0xc2, 0x13, 0x5e,
0x09, 0x41, 0x79, 0xd3, 0x9e, 0xc1, 0x63, 0xca, 0x65, 0xc6, 0x65, 0xa0, 0x0f, 0x4d, 0xba, 0xbf,
0xda, 0xa8, 0xf7, 0x49, 0x26, 0xd3, 0x3a, 0x14, 0xbf, 0x46, 0x3d, 0xc9, 0x73, 0x41, 0xe1, 0x72,
0xc9, 0x85, 0xb2, 0x8c, 0xa1, 0xe1, 0xdd, 0x9f, 0x9c, 0x6c, 0x0a, 0x07, 0x5f, 0x85, 0xd9, 0xe2,
0x8d, 0x7b, 0x4b, 0x74, 0x09, 0xd2, 0xe8, 0x82, 0x0b, 0x85, 0xdf, 0xa2, 0x07, 0xb5, 0x46, 0x67,
0x21, 0x63, 0xb0, 0xb0, 0xee, 0x55, 0xb1, 0xcf, 0x36, 0x85, 0xf3, 0xa4, 0x11, 0x5b, 0xeb, 0x2e,
0x39, 0xd2, 0xc4, 0x99, 0xc6, 0xd8, 0x43, 0x1d, 0xc5, 0xe7, 0xc0, 0xac, 0xf6, 0xd0, 0xf0, 0x7a,
0x2f, 0xfb, 0x7e, 0x5d, 0xe8, 0x19, 0x4f, 0xd9, 0xc4, 0xbc, 0x2e, 0x9c, 0x16, 0xd1, 0x06, 0x7c,
0x8e, 0xba, 0x12, 0x58, 0x0c, 0xc2, 0x32, 0x87, 0x86, 0xd7, 0x9f, 0x9c, 0xfe, 0x2d, 0x9c, 0x51,
0x92, 0xaa, 0x59, 0x1e, 0xf9, 0x94, 0x67, 0x41, 0xa3, 0xd1, 0x91, 0x8c, 0xe7, 0x81, 0xba, 0x5a,
0x82, 0xf4, 0xc7, 0x94, 0x8e, 0xe3, 0x58, 0x80, 0x94, 0xa4, 0x4e, 0x80, 0x07, 0xe8, 0x50, 0x00,
0x85, 0xf4, 0x2b, 0x08, 0xab, 0x53, 0x16, 0x4c, 0x76, 0xb8, 0x6c, 0x49, 0xa5, 0x19, 0xf0, 0x5c,
0x5d, 0xce, 0x20, 0x4d, 0x66, 0xca, 0xea, 0x0e, 0x0d, 0xcf, 0xbc, 0xdd, 0x52, 0x53, 0x77, 0xc9,
0x51, 0x4d, 0x7c, 0xa8, 0x30, 0x3e, 0x47, 0x8f, 0xb6, 0x8e, 0xf2, 0x94, 0x2a, 0xcc, 0x96, 0xd6,
0x41, 0x95, 0xe4, 0xc5, 0xa6, 0x70, 0xac, 0x66, 0x92, 0x9d, 0xc5, 0x25, 0x0f, 0x6b, 0x6e, 0xba,
0xa3, 0x7e, 0xa0, 0xa7, 0xef, 0x73, 0x96, 0xa4, 0xd1, 0x02, 0xa6, 0xe5, 0x10, 0x2e, 0x42, 0x3a,
0x07, 0xf5, 0x2e, 0x54, 0x21, 0x3e, 0x46, 0x9d, 0x18, 0x18, 0xcf, 0xf4, 0xb6, 0x88, 0x06, 0xf8,
0x04, 0x75, 0xc3, 0x8c, 0xe7, 0x4c, 0x55, 0x8b, 0x30, 0x49, 0x8d, 0x4a, 0xbe, 0x1e, 0x5e, 0xbb,
0xb2, 0xef, 0x9b, 0x84, 0xd9, 0x9c, 0x84, 0x3b, 0x45, 0xee, 0x9e, 0x8f, 0x8f, 0xe9, 0x9c, 0xf1,
0x6f, 0x0b, 0x88, 0x13, 0xc8, 0x80, 0x29, 0x6c, 0xa1, 0x03, 0x99, 0x53, 0x0a, 0x52, 0x56, 0x95,
0x1c, 0x92, 0x2d, 0x2c, 0x2b, 0x04, 0x21, 0xb8, 0xd0, 0x6f, 0x82, 0x68, 0x30, 0xf9, 0x78, 0xbd,
0xb2, 0x8d, 0x9b, 0x95, 0x6d, 0xfc, 0x59, 0xd9, 0xc6, 0xcf, 0xb5, 0xdd, 0xba, 0x59, 0xdb, 0xad,
0xdf, 0x6b, 0xbb, 0xf5, 0xf9, 0xf4, 0xce, 0x65, 0x7e, 0x0f, 0xd2, 0x88, 0x8e, 0xfe, 0xff, 0x0a,
0xe5, 0x6e, 0xa3, 0x6e, 0xf5, 0x9e, 0x5f, 0xfd, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xc4, 0xe2,
0x00, 0x27, 0x03, 0x00, 0x00,
}
func (m *MsgTransfer) Marshal() (dAtA []byte, err error) {
@ -335,20 +347,16 @@ func (m *MsgTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x22
}
if len(m.Amount) > 0 {
for iNdEx := len(m.Amount) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Amount[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTransfer(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1a
{
size, err := m.Token.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTransfer(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1a
if len(m.SourceChannel) > 0 {
i -= len(m.SourceChannel)
copy(dAtA[i:], m.SourceChannel)
@ -391,28 +399,26 @@ func (m *FungibleTokenPacketData) MarshalToSizedBuffer(dAtA []byte) (int, error)
copy(dAtA[i:], m.Receiver)
i = encodeVarintTransfer(dAtA, i, uint64(len(m.Receiver)))
i--
dAtA[i] = 0x1a
dAtA[i] = 0x22
}
if len(m.Sender) > 0 {
i -= len(m.Sender)
copy(dAtA[i:], m.Sender)
i = encodeVarintTransfer(dAtA, i, uint64(len(m.Sender)))
i--
dAtA[i] = 0x12
dAtA[i] = 0x1a
}
if len(m.Amount) > 0 {
for iNdEx := len(m.Amount) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Amount[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTransfer(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
}
if m.Amount != 0 {
i = encodeVarintTransfer(dAtA, i, uint64(m.Amount))
i--
dAtA[i] = 0x10
}
if len(m.Denom) > 0 {
i -= len(m.Denom)
copy(dAtA[i:], m.Denom)
i = encodeVarintTransfer(dAtA, i, uint64(len(m.Denom)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
@ -482,12 +488,8 @@ func (m *MsgTransfer) Size() (n int) {
if l > 0 {
n += 1 + l + sovTransfer(uint64(l))
}
if len(m.Amount) > 0 {
for _, e := range m.Amount {
l = e.Size()
n += 1 + l + sovTransfer(uint64(l))
}
}
l = m.Token.Size()
n += 1 + l + sovTransfer(uint64(l))
l = len(m.Sender)
if l > 0 {
n += 1 + l + sovTransfer(uint64(l))
@ -511,11 +513,12 @@ func (m *FungibleTokenPacketData) Size() (n int) {
}
var l int
_ = l
if len(m.Amount) > 0 {
for _, e := range m.Amount {
l = e.Size()
n += 1 + l + sovTransfer(uint64(l))
}
l = len(m.Denom)
if l > 0 {
n += 1 + l + sovTransfer(uint64(l))
}
if m.Amount != 0 {
n += 1 + sovTransfer(uint64(m.Amount))
}
l = len(m.Sender)
if l > 0 {
@ -645,7 +648,7 @@ func (m *MsgTransfer) Unmarshal(dAtA []byte) error {
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
@ -672,8 +675,7 @@ func (m *MsgTransfer) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Amount = append(m.Amount, types.Coin{})
if err := m.Amount[len(m.Amount)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
if err := m.Token.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
@ -836,9 +838,9 @@ func (m *FungibleTokenPacketData) Unmarshal(dAtA []byte) error {
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType)
}
var msglen int
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTransfer
@ -848,27 +850,44 @@ func (m *FungibleTokenPacketData) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTransfer
}
postIndex := iNdEx + msglen
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTransfer
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Amount = append(m.Amount, types.Coin{})
if err := m.Amount[len(m.Amount)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
m.Denom = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType)
}
m.Amount = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTransfer
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Amount |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType)
}
@ -900,7 +919,7 @@ func (m *FungibleTokenPacketData) Unmarshal(dAtA []byte) error {
}
m.Sender = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType)
}

View File

@ -326,8 +326,11 @@ func (suite *HandlerTestSuite) TestHandleTimeoutPacket() {
clientA, clientB, connA, connB := suite.coordinator.SetupClientConnections(suite.chainA, suite.chainB, clientexported.Tendermint)
channelA, channelB := suite.coordinator.CreateChannel(suite.chainA, suite.chainB, connA, connB, channeltypes.ORDERED)
packet = channeltypes.NewPacket(suite.chainA.GetPacketData(suite.chainB), 1, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, uint64(suite.chainB.GetContext().BlockHeight()), uint64(suite.chainB.GetContext().BlockTime().UnixNano()))
err := suite.coordinator.SendPacket(suite.chainA, suite.chainB, packet, clientB)
suite.Require().NoError(err)
// send from chainA to chainB
msg := ibctransfertypes.NewMsgTransfer(channelA.PortID, channelA.ID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), packet.GetTimeoutHeight(), packet.GetTimeoutTimestamp())
err := suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
suite.Require().NoError(err) // message committed
// need to update chainA client to prove missing ack
suite.coordinator.UpdateClient(suite.chainA, suite.chainB, clientA, clientexported.Tendermint)
@ -337,11 +340,15 @@ func (suite *HandlerTestSuite) TestHandleTimeoutPacket() {
{"success: UNORDERED", func() {
clientA, clientB, _, _, channelA, channelB := suite.coordinator.Setup(suite.chainA, suite.chainB)
packet = channeltypes.NewPacket(suite.chainA.GetPacketData(suite.chainB), 1, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, uint64(suite.chainB.GetContext().BlockHeight()), uint64(suite.chainB.GetContext().BlockTime().UnixNano()))
err := suite.coordinator.SendPacket(suite.chainA, suite.chainB, packet, clientB)
suite.Require().NoError(err)
// send from chainA to chainB
msg := ibctransfertypes.NewMsgTransfer(channelA.PortID, channelA.ID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), packet.GetTimeoutHeight(), packet.GetTimeoutTimestamp())
err := suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
suite.Require().NoError(err) // message committed
// need to update chainA client to prove missing ack
suite.coordinator.UpdateClient(suite.chainA, suite.chainB, clientA, clientexported.Tendermint)
packetKey = host.KeyPacketAcknowledgement(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
}, true},
{"success: UNORDERED timeout out of order packet", func() {
@ -352,8 +359,10 @@ func (suite *HandlerTestSuite) TestHandleTimeoutPacket() {
for i := uint64(1); i < 10; i++ {
packet = channeltypes.NewPacket(suite.chainA.GetPacketData(suite.chainB), i, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, uint64(suite.chainB.GetContext().BlockHeight()), 0)
err := suite.coordinator.SendPacket(suite.chainA, suite.chainB, packet, clientB)
suite.Require().NoError(err)
// send from chainA to chainB
msg := ibctransfertypes.NewMsgTransfer(channelA.PortID, channelA.ID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), packet.GetTimeoutHeight(), packet.GetTimeoutTimestamp())
err := suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
suite.Require().NoError(err) // message committed
}
// need to update chainA client to prove missing ack
suite.coordinator.UpdateClient(suite.chainA, suite.chainB, clientA, clientexported.Tendermint)
@ -368,8 +377,10 @@ func (suite *HandlerTestSuite) TestHandleTimeoutPacket() {
for i := uint64(1); i < 10; i++ {
packet = channeltypes.NewPacket(suite.chainA.GetPacketData(suite.chainB), i, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, uint64(suite.chainB.GetContext().BlockHeight()), 0)
err := suite.coordinator.SendPacket(suite.chainA, suite.chainB, packet, clientB)
suite.Require().NoError(err)
// send from chainA to chainB
msg := ibctransfertypes.NewMsgTransfer(channelA.PortID, channelA.ID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), packet.GetTimeoutHeight(), packet.GetTimeoutTimestamp())
err := suite.coordinator.SendMsgs(suite.chainA, suite.chainB, clientB, msg)
suite.Require().NoError(err) // message committed
}
// need to update chainA client to prove missing ack
suite.coordinator.UpdateClient(suite.chainA, suite.chainB, clientA, clientexported.Tendermint)

View File

@ -54,6 +54,7 @@ const (
var (
DefaultTrustLevel tmmath.Fraction = lite.DefaultTrustLevel
TestHash = []byte("TESTING HASH")
TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))
ConnectionVersion = connectiontypes.GetCompatibleEncodedVersions()[0]
)
@ -594,9 +595,14 @@ func (chain *TestChain) ChanCloseInit(
}
// GetPacketData returns a ibc-transfer marshalled packet to be used for
// callback testing
// callback testing.
func (chain *TestChain) GetPacketData(counterparty *TestChain) []byte {
packet := ibctransfertypes.NewFungibleTokenPacketData(sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))), chain.SenderAccount.GetAddress().String(), counterparty.SenderAccount.GetAddress().String())
packet := ibctransfertypes.FungibleTokenPacketData{
Denom: TestCoin.Denom,
Amount: TestCoin.Amount.Uint64(),
Sender: chain.SenderAccount.GetAddress().String(),
Receiver: counterparty.SenderAccount.GetAddress().String(),
}
return packet.GetBytes()
}

View File

@ -13,6 +13,7 @@ import (
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var (
@ -244,6 +245,55 @@ func (coord *Coordinator) AcknowledgementExecuted(
)
}
// RecvPacket receives a channel packet on the counterparty chain and updates
// the client on the source chain representing the counterparty.
func (coord *Coordinator) RecvPacket(
source, counterparty *TestChain,
sourceClient string,
packet channeltypes.Packet,
) error {
// get proof of packet commitment on source
packetKey := host.KeyPacketCommitment(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
proof, proofHeight := source.QueryProof(packetKey)
recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, counterparty.SenderAccount.GetAddress())
// receive on counterparty and update source client
return coord.SendMsgs(counterparty, source, sourceClient, recvMsg)
}
// AcknowledgePacket acknowledges on the source chain the packet received on
// the counterparty chain and updates the client on the counterparty representing
// the source chain.
// TODO: add a query for the acknowledgement by events
// - https://github.com/cosmos/cosmos-sdk/issues/6509
func (coord *Coordinator) AcknowledgePacket(
source, counterparty *TestChain,
counterpartyClient string,
packet channeltypes.Packet, ack []byte,
) error {
// get proof of acknowledgement on counterparty
packetKey := host.KeyPacketAcknowledgement(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
proof, proofHeight := counterparty.QueryProof(packetKey)
ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, source.SenderAccount.GetAddress())
return coord.SendMsgs(source, counterparty, counterpartyClient, ackMsg)
}
// RelayPacket receives a channel packet on counterparty, queries the ack
// and acknowledges the packet on source. The clients are updated as needed.
func (coord *Coordinator) RelayPacket(
source, counterparty *TestChain,
sourceClient, counterpartyClient string,
packet channeltypes.Packet, ack []byte,
) error {
if err := coord.RecvPacket(source, counterparty, sourceClient, packet); err != nil {
return err
}
return coord.AcknowledgePacket(source, counterparty, counterpartyClient, packet, ack)
}
// IncrementTime iterates through all the TestChain's and increments their current header time
// by 5 seconds.
//

View File

@ -1,11 +0,0 @@
package testing
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func NewTransferCoins(dst TestChannel, denom string, amount int64) sdk.Coins {
return sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%s/%s/%s", dst.PortID, dst.ID, denom), sdk.NewInt(amount)))
}