Some checks failed
CodeQL / Analyze (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Deploy to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Buf-Push / push (push) Has been cancelled
421 lines
16 KiB
Go
421 lines
16 KiB
Go
package v2_test
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
testifysuite "github.com/stretchr/testify/suite"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
|
|
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
|
|
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
|
|
channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"
|
|
ibctesting "github.com/cosmos/ibc-go/v10/testing"
|
|
)
|
|
|
|
const testclientid = "testclientid"
|
|
|
|
type TransferTestSuite struct {
|
|
testifysuite.Suite
|
|
|
|
coordinator *ibctesting.Coordinator
|
|
|
|
// testing chains used for convenience and readability
|
|
chainA *ibctesting.TestChain
|
|
chainB *ibctesting.TestChain
|
|
chainC *ibctesting.TestChain
|
|
|
|
pathAToB *ibctesting.Path
|
|
pathBToC *ibctesting.Path
|
|
}
|
|
|
|
const invalidPortID = "invalidportid"
|
|
|
|
func (suite *TransferTestSuite) SetupTest() {
|
|
suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3)
|
|
suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1))
|
|
suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2))
|
|
suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3))
|
|
|
|
// setup between chainA and chainB
|
|
// NOTE:
|
|
// pathAToB.EndpointA = endpoint on chainA
|
|
// pathAToB.EndpointB = endpoint on chainB
|
|
suite.pathAToB = ibctesting.NewPath(suite.chainA, suite.chainB)
|
|
|
|
// setup between chainB and chainC
|
|
// pathBToC.EndpointA = endpoint on chainB
|
|
// pathBToC.EndpointB = endpoint on chainC
|
|
suite.pathBToC = ibctesting.NewPath(suite.chainB, suite.chainC)
|
|
|
|
// setup IBC v2 paths between the chains
|
|
suite.pathAToB.SetupV2()
|
|
suite.pathBToC.SetupV2()
|
|
}
|
|
|
|
func TestTransferTestSuite(t *testing.T) {
|
|
testifysuite.Run(t, new(TransferTestSuite))
|
|
}
|
|
|
|
func (suite *TransferTestSuite) TestOnSendPacket() {
|
|
var payload channeltypesv2.Payload
|
|
testCases := []struct {
|
|
name string
|
|
sourceDenomToTransfer string
|
|
malleate func()
|
|
expError error
|
|
}{
|
|
{
|
|
"transfer single denom",
|
|
sdk.DefaultBondDenom,
|
|
func() {},
|
|
nil,
|
|
},
|
|
{
|
|
"transfer with invalid source port",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
payload.SourcePort = invalidPortID
|
|
},
|
|
channeltypesv2.ErrInvalidPacket,
|
|
},
|
|
{
|
|
"transfer with invalid destination port",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
payload.DestinationPort = invalidPortID
|
|
},
|
|
channeltypesv2.ErrInvalidPacket,
|
|
},
|
|
{
|
|
"transfer with invalid source client",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
suite.pathAToB.EndpointA.ClientID = testclientid
|
|
},
|
|
channeltypesv2.ErrInvalidPacket,
|
|
},
|
|
{
|
|
"transfer with invalid destination client",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
suite.pathAToB.EndpointB.ClientID = testclientid
|
|
},
|
|
channeltypesv2.ErrInvalidPacket,
|
|
},
|
|
{
|
|
"transfer with slashes in base denom",
|
|
"base/coin",
|
|
func() {},
|
|
types.ErrInvalidDenomForTransfer,
|
|
},
|
|
{
|
|
"transfer with slashes in ibc denom",
|
|
fmt.Sprintf("ibc/%x", sha256.Sum256([]byte("coin"))),
|
|
func() {},
|
|
types.ErrInvalidDenomForTransfer,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest() // reset
|
|
|
|
originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer)
|
|
|
|
amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64)
|
|
suite.Require().True(ok)
|
|
originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount)
|
|
|
|
token := types.Token{
|
|
Denom: types.Denom{Base: originalCoin.Denom},
|
|
Amount: originalCoin.Amount.String(),
|
|
}
|
|
|
|
transferData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "")
|
|
bz := suite.chainA.Codec.MustMarshal(&transferData)
|
|
payload = channeltypesv2.NewPayload(types.PortID, types.PortID, types.V1, types.EncodingProtobuf, bz)
|
|
|
|
// malleate payload
|
|
tc.malleate()
|
|
|
|
ctx := suite.chainA.GetContext()
|
|
cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
|
|
|
|
err := cbs.OnSendPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, 1, payload, suite.chainA.SenderAccount.GetAddress())
|
|
|
|
if tc.expError != nil {
|
|
suite.Require().Contains(err.Error(), tc.expError.Error())
|
|
return
|
|
}
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID)
|
|
// check that the balance for chainA is updated
|
|
chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64())
|
|
|
|
// check that module account escrow address has locked the tokens
|
|
chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(originalCoin, chainAEscrowBalance)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *TransferTestSuite) TestOnRecvPacket() {
|
|
var payload channeltypesv2.Payload
|
|
testCases := []struct {
|
|
name string
|
|
sourceDenomToTransfer string
|
|
malleate func()
|
|
expErr bool
|
|
}{
|
|
{
|
|
"transfer single denom",
|
|
sdk.DefaultBondDenom,
|
|
func() {},
|
|
false,
|
|
},
|
|
{
|
|
"transfer with invalid source port",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
payload.SourcePort = invalidPortID
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"transfer with invalid dest port",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
payload.DestinationPort = invalidPortID
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"transfer with invalid source client",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
suite.pathAToB.EndpointA.ClientID = testclientid
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"transfer with invalid destination client",
|
|
sdk.DefaultBondDenom,
|
|
func() {
|
|
suite.pathAToB.EndpointB.ClientID = testclientid
|
|
},
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest() // reset
|
|
|
|
originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer)
|
|
|
|
timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix())
|
|
|
|
amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64)
|
|
suite.Require().True(ok)
|
|
originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount)
|
|
|
|
msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf)
|
|
resp, err := suite.chainA.SendMsgs(msg)
|
|
suite.Require().NoError(err) // message committed
|
|
|
|
packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Len(packets, 1)
|
|
suite.Require().Len(packets[0].Payloads, 1)
|
|
payload = packets[0].Payloads[0]
|
|
|
|
ctx := suite.chainB.GetContext()
|
|
cbs := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
|
|
|
|
// malleate payload after it has been sent but before OnRecvPacket callback is called
|
|
tc.malleate()
|
|
|
|
recvResult := cbs.OnRecvPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, packets[0].Sequence, payload, suite.chainB.SenderAccount.GetAddress())
|
|
|
|
if tc.expErr {
|
|
suite.Require().Equal(channeltypesv2.PacketStatus_Failure, recvResult.Status)
|
|
return
|
|
}
|
|
|
|
suite.Require().Equal(channeltypesv2.PacketStatus_Success, recvResult.Status)
|
|
suite.Require().Equal(channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement(), recvResult.Acknowledgement)
|
|
|
|
escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID)
|
|
// check that the balance for chainA is updated
|
|
chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64())
|
|
|
|
// check that module account escrow address has locked the tokens
|
|
chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(originalCoin, chainAEscrowBalance)
|
|
|
|
traceAToB := types.NewHop(types.PortID, suite.pathAToB.EndpointB.ClientID)
|
|
|
|
// check that voucher exists on chain B
|
|
chainBDenom := types.NewDenom(originalCoin.Denom, traceAToB)
|
|
chainBBalance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), chainBDenom.IBCDenom())
|
|
coinSentFromAToB := sdk.NewCoin(chainBDenom.IBCDenom(), amount)
|
|
suite.Require().Equal(coinSentFromAToB, chainBBalance)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *TransferTestSuite) TestOnAckPacket() {
|
|
testCases := []struct {
|
|
name string
|
|
sourceDenomToTransfer string
|
|
}{
|
|
{
|
|
"transfer single denom",
|
|
sdk.DefaultBondDenom,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest() // reset
|
|
|
|
originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer)
|
|
|
|
timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix())
|
|
|
|
amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64)
|
|
suite.Require().True(ok)
|
|
originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount)
|
|
|
|
msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf)
|
|
|
|
resp, err := suite.chainA.SendMsgs(msg)
|
|
suite.Require().NoError(err) // message committed
|
|
packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Len(packets, 1)
|
|
suite.Require().Len(packets[0].Payloads, 1)
|
|
payload := packets[0].Payloads[0]
|
|
|
|
ctx := suite.chainA.GetContext()
|
|
cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
|
|
|
|
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
|
|
|
|
err = cbs.OnAcknowledgementPacket(
|
|
ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID,
|
|
packets[0].Sequence, ack.Acknowledgement(), payload, suite.chainA.SenderAccount.GetAddress(),
|
|
)
|
|
suite.Require().NoError(err)
|
|
|
|
// on successful ack, the tokens sent in packets should still be in escrow
|
|
escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID)
|
|
// check that the balance for chainA is updated
|
|
chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64())
|
|
|
|
// check that module account escrow address has locked the tokens
|
|
chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(originalCoin, chainAEscrowBalance)
|
|
|
|
// create a custom error ack and replay the callback to ensure it fails with IBC v2 callbacks
|
|
errAck := channeltypes.NewErrorAcknowledgement(types.ErrInvalidAmount)
|
|
err = cbs.OnAcknowledgementPacket(
|
|
ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID,
|
|
1, errAck.Acknowledgement(), payload, suite.chainA.SenderAccount.GetAddress(),
|
|
)
|
|
suite.Require().Error(err)
|
|
|
|
// create the sentinel error ack and replay the callback to ensure the tokens are correctly refunded
|
|
// we can replay the callback here because the replay protection is handled in the IBC handler
|
|
err = cbs.OnAcknowledgementPacket(
|
|
ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID,
|
|
1, channeltypesv2.ErrorAcknowledgement[:], payload, suite.chainA.SenderAccount.GetAddress(),
|
|
)
|
|
suite.Require().NoError(err)
|
|
|
|
// on error ack, the tokens sent in packets should be returned to sender
|
|
// check that the balance for chainA is refunded
|
|
chainABalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount, chainABalance.Amount)
|
|
|
|
// check that module account escrow address has no tokens
|
|
chainAEscrowBalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(sdk.NewCoin(originalCoin.Denom, sdkmath.ZeroInt()), chainAEscrowBalance)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (suite *TransferTestSuite) TestOnTimeoutPacket() {
|
|
testCases := []struct {
|
|
name string
|
|
sourceDenomToTransfer string
|
|
}{
|
|
{
|
|
"transfer single denom",
|
|
sdk.DefaultBondDenom,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest() // reset
|
|
|
|
originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer)
|
|
|
|
timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix())
|
|
|
|
amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64)
|
|
suite.Require().True(ok)
|
|
originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount)
|
|
|
|
msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf)
|
|
resp, err := suite.chainA.SendMsgs(msg)
|
|
suite.Require().NoError(err) // message committed
|
|
packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Len(packets, 1)
|
|
suite.Require().Len(packets[0].Payloads, 1)
|
|
payload := packets[0].Payloads[0]
|
|
|
|
// on successful send, the tokens sent in packets should be in escrow
|
|
escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID)
|
|
// check that the balance for chainA is updated
|
|
chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64())
|
|
|
|
// check that module account escrow address has locked the tokens
|
|
chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(originalCoin, chainAEscrowBalance)
|
|
|
|
ctx := suite.chainA.GetContext()
|
|
cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
|
|
|
|
err = cbs.OnTimeoutPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, packets[0].Sequence, payload, suite.chainA.SenderAccount.GetAddress())
|
|
suite.Require().NoError(err)
|
|
|
|
// on timeout, the tokens sent in packets should be returned to sender
|
|
// check that the balance for chainA is refunded
|
|
chainABalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom)
|
|
suite.Require().Equal(originalBalance.Amount, chainABalance.Amount)
|
|
|
|
// check that module account escrow address has no tokens
|
|
chainAEscrowBalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom)
|
|
suite.Require().Equal(sdk.NewCoin(originalCoin.Denom, sdkmath.ZeroInt()), chainAEscrowBalance)
|
|
})
|
|
}
|
|
}
|