cosmos-sdk/x/ibc-transfer/keeper/relay_test.go

301 lines
11 KiB
Go

package keeper_test
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"
connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
func (suite *KeeperTestSuite) TestSendTransfer() {
testCoins2 := sdk.NewCoins(sdk.NewCoin("testportid/secondchannel/atom", sdk.NewInt(100)))
capName := host.ChannelCapabilityPath(testPort1, testChannel1)
testCases := []struct {
msg string
amount sdk.Coins
malleate func()
isSourceChain bool
expPass bool
}{
{"successful transfer from source chain", testCoins2,
func() {
suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), testAddr1, testCoins)
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
suite.chainA.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), testPort1, testChannel1, 1)
}, true, true},
{"successful transfer from external chain", prefixCoins,
func() {
suite.chainA.App.BankKeeper.SetSupply(suite.chainA.GetContext(), banktypes.NewSupply(prefixCoins))
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), testAddr1, prefixCoins)
suite.Require().NoError(err)
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
suite.chainA.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), testPort1, testChannel1, 1)
}, false, true},
{"source channel not found", testCoins,
func() {}, true, false},
{"next seq send not found", testCoins,
func() {
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
}, true, false},
// createOutgoingPacket tests
// - source chain
{"send coins failed", testCoins,
func() {
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
suite.chainA.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), testPort1, testChannel1, 1)
}, true, false},
// - receiving chain
{"send from module account failed", testCoins,
func() {
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
suite.chainA.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), testPort1, testChannel1, 1)
}, false, false},
{"channel capability not found", testCoins,
func() {
suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), testAddr1, testCoins)
suite.chainA.CreateClient(suite.chainB)
suite.chainA.createConnection(testConnection, testConnection, testClientIDB, testClientIDA, connectiontypes.OPEN)
suite.chainA.createChannel(testPort1, testChannel1, testPort2, testChannel2, channeltypes.OPEN, channeltypes.ORDERED, testConnection)
suite.chainA.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), testPort1, testChannel1, 1)
// Release channel capability
cap, _ := suite.chainA.App.ScopedTransferKeeper.GetCapability(suite.chainA.GetContext(), capName)
suite.chainA.App.ScopedTransferKeeper.ReleaseCapability(suite.chainA.GetContext(), cap)
}, true, false},
}
for i, tc := range testCases {
tc := tc
i := i
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
// create channel capability from ibc scoped keeper and claim with transfer scoped keeper
cap, err := suite.chainA.App.ScopedIBCKeeper.NewCapability(suite.chainA.GetContext(), capName)
suite.Require().Nil(err, "could not create capability")
err = suite.chainA.App.ScopedTransferKeeper.ClaimCapability(suite.chainA.GetContext(), cap, capName)
suite.Require().Nil(err, "transfer module could not claim capability")
tc.malleate()
err = suite.chainA.App.TransferKeeper.SendTransfer(
suite.chainA.GetContext(), testPort1, testChannel1, tc.amount, testAddr1, testAddr2.String(), 110, 0,
)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.msg)
}
})
}
}
func (suite *KeeperTestSuite) TestOnRecvPacket() {
data := types.NewFungibleTokenPacketData(prefixCoins2, testAddr1.String(), testAddr2.String())
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"success receive from source chain",
func() {}, true},
// onRecvPacket
// - source chain
{"no dest prefix on coin denom",
func() {
data.Amount = testCoins
}, false},
{"mint failed",
func() {
data.Amount = prefixCoins2
data.Amount[0].Amount = sdk.ZeroInt()
}, false},
// - receiving chain
{"incorrect dest prefix on coin denom",
func() {
data.Amount = prefixCoins
}, false},
{"success receive from external chain",
func() {
data.Amount = prefixCoins
escrow := types.GetEscrowAddress(testPort2, testChannel2)
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), escrow, testCoins)
suite.Require().NoError(err)
}, true},
}
packet := channeltypes.NewPacket(data.GetBytes(), 1, testPort1, testChannel1, testPort2, testChannel2, 100, 0)
for i, tc := range testCases {
tc := tc
i := i
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
err := suite.chainA.App.TransferKeeper.OnRecvPacket(suite.chainA.GetContext(), packet, data)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg)
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.msg)
}
})
}
}
// TestOnAcknowledgementPacket tests that successful acknowledgement is a no-op
// and failure acknowledment leads to refund
func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() {
data := types.NewFungibleTokenPacketData(prefixCoins2, testAddr1.String(), testAddr2.String())
successAck := types.FungibleTokenPacketAcknowledgement{
Success: true,
}
failedAck := types.FungibleTokenPacketAcknowledgement{
Success: false,
Error: "failed packet transfer",
}
testCases := []struct {
msg string
ack types.FungibleTokenPacketAcknowledgement
malleate func()
source bool
success bool // success of ack
}{
{"success ack causes no-op", successAck,
func() {}, true, true},
{"successful refund from source chain", failedAck,
func() {
escrow := types.GetEscrowAddress(testPort1, testChannel1)
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), escrow, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(100))))
suite.Require().NoError(err)
}, true, false},
{"successful refund from external chain", failedAck,
func() {
data.Amount = prefixCoins
}, false, false},
}
packet := channeltypes.NewPacket(data.GetBytes(), 1, testPort1, testChannel1, testPort2, testChannel2, 100, 0)
for i, tc := range testCases {
tc := tc
i := i
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
var denom string
if tc.source {
prefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
denom = prefixCoins2[0].Denom[len(prefix):]
} else {
denom = data.Amount[0].Denom
}
preCoin := suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), testAddr1, denom)
err := suite.chainA.App.TransferKeeper.OnAcknowledgementPacket(suite.chainA.GetContext(), packet, data, tc.ack)
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg)
postCoin := suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), testAddr1, denom)
deltaAmount := postCoin.Amount.Sub(preCoin.Amount)
if tc.success {
suite.Require().Equal(sdk.ZeroInt(), deltaAmount, "successful ack changed balance")
} else {
suite.Require().Equal(prefixCoins2[0].Amount, deltaAmount, "failed ack did not trigger refund")
}
})
}
}
// TestOnTimeoutPacket test private refundPacket function since it is a simple wrapper over it
func (suite *KeeperTestSuite) TestOnTimeoutPacket() {
data := types.NewFungibleTokenPacketData(prefixCoins2, testAddr1.String(), testAddr2.String())
testCoins2 := sdk.NewCoins(sdk.NewCoin("bank/firstchannel/atom", sdk.NewInt(100)))
testCases := []struct {
msg string
malleate func()
source bool
expPass bool
}{
{"successful timeout from source chain",
func() {
escrow := types.GetEscrowAddress(testPort1, testChannel1)
_, err := suite.chainA.App.BankKeeper.AddCoins(suite.chainA.GetContext(), escrow, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(100))))
suite.Require().NoError(err)
}, true, true},
{"successful timeout from external chain",
func() {
data.Amount = testCoins2
}, false, true},
{"no source prefix on coin denom",
func() {
data.Amount = prefixCoins2
}, false, false},
{"unescrow failed",
func() {
}, true, false},
{"mint failed",
func() {
data.Amount[0].Denom = prefixCoins2[0].Denom
data.Amount[0].Amount = sdk.ZeroInt()
}, true, false},
}
packet := channeltypes.NewPacket(data.GetBytes(), 1, testPort1, testChannel1, testPort2, testChannel2, 100, 0)
for i, tc := range testCases {
tc := tc
i := i
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
var denom string
if tc.source {
prefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
denom = prefixCoins2[0].Denom[len(prefix):]
} else {
denom = data.Amount[0].Denom
}
preCoin := suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), testAddr1, denom)
err := suite.chainA.App.TransferKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet, data)
postCoin := suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), testAddr1, denom)
deltaAmount := postCoin.Amount.Sub(preCoin.Amount)
if tc.expPass {
suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg)
suite.Require().Equal(prefixCoins2[0].Amount.Int64(), deltaAmount.Int64(), "successful timeout did not trigger refund")
} else {
suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.msg)
}
})
}
}