Some checks failed
CodeQL / Analyze (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
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
1143 lines
35 KiB
Go
1143 lines
35 KiB
Go
package ibccallbacks_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
storetypes "cosmossdk.io/store/types"
|
|
|
|
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
|
|
|
|
icacontrollertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/controller/types"
|
|
ibccallbacks "git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/internal"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/testing/simapp"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/types"
|
|
transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
|
|
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
|
|
channelkeeper "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/keeper"
|
|
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
|
|
porttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/05-port/types"
|
|
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
|
|
ibcexported "git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
|
|
ibctesting "git.cw.tr/mukan-network/mukan-ibc/testing"
|
|
ibcmock "git.cw.tr/mukan-network/mukan-ibc/testing/mock"
|
|
)
|
|
|
|
func (s *CallbacksTestSuite) TestNewIBCMiddleware() {
|
|
testCases := []struct {
|
|
name string
|
|
instantiateFn func()
|
|
expError error
|
|
}{
|
|
{
|
|
"success",
|
|
func() {
|
|
_ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, maxCallbackGas)
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"panics with nil underlying app",
|
|
func() {
|
|
_ = ibccallbacks.NewIBCMiddleware(nil, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, maxCallbackGas)
|
|
},
|
|
fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil)),
|
|
},
|
|
{
|
|
"panics with nil contract keeper",
|
|
func() {
|
|
_ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, nil, maxCallbackGas)
|
|
},
|
|
errors.New("contract keeper cannot be nil"),
|
|
},
|
|
{
|
|
"panics with nil ics4Wrapper",
|
|
func() {
|
|
_ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, nil, simapp.ContractKeeper{}, maxCallbackGas)
|
|
},
|
|
errors.New("ICS4Wrapper cannot be nil"),
|
|
},
|
|
{
|
|
"panics with zero maxCallbackGas",
|
|
func() {
|
|
_ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, uint64(0))
|
|
},
|
|
errors.New("maxCallbackGas cannot be zero"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
if tc.expError == nil {
|
|
s.Require().NotPanics(tc.instantiateFn, "unexpected panic: NewIBCMiddleware")
|
|
} else {
|
|
s.Require().PanicsWithError(tc.expError.Error(), tc.instantiateFn, "expected panic with error: ", tc.expError.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestWithICS4Wrapper() {
|
|
s.setupChains()
|
|
|
|
cbsMiddleware := ibccallbacks.IBCMiddleware{}
|
|
s.Require().Nil(cbsMiddleware.GetICS4Wrapper())
|
|
|
|
cbsMiddleware.WithICS4Wrapper(s.chainA.App.GetIBCKeeper().ChannelKeeper)
|
|
ics4Wrapper := cbsMiddleware.GetICS4Wrapper()
|
|
|
|
s.Require().IsType((*channelkeeper.Keeper)(nil), ics4Wrapper)
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestSendPacket() {
|
|
var packetData transfertypes.FungibleTokenPacketData
|
|
var callbackExecuted bool
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
callbackType types.CallbackType
|
|
expPanic bool
|
|
expValue any
|
|
}{
|
|
{
|
|
"success",
|
|
func() {},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"success: callback data doesn't exist",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = ""
|
|
},
|
|
"none", // nonexistent callback data should result in no callback execution
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"failure: callback data is not valid",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = `{"src_callback": {"address": ""}}`
|
|
},
|
|
"none", // improperly formatted callback data should result in no callback execution
|
|
false,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: ics4Wrapper SendPacket call fails",
|
|
func() {
|
|
s.path.EndpointA.ChannelID = "invalid-channel"
|
|
},
|
|
"none", // ics4wrapper failure should result in no callback execution
|
|
false,
|
|
channeltypes.ErrChannelNotFound,
|
|
},
|
|
{
|
|
"failure: callback execution fails",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract)
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
ibcmock.MockApplicationCallbackError, // execution failure on SendPacket should prevent packet sends
|
|
},
|
|
{
|
|
"failure: callback execution reach out of gas panic, but sufficient gas provided",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract)
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
true,
|
|
storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("mock %s callback oog panic", types.CallbackTypeSendPacket)},
|
|
},
|
|
{
|
|
"failure: callback execution reach out of gas error, but sufficient gas provided",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogErrorContract)
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", types.CallbackTypeSendPacket),
|
|
},
|
|
{
|
|
"failure: callback address invalid",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":%d}}`, 50)
|
|
callbackExecuted = false // callback should not be executed
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: callback gas limit invalid",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":%d}}`, simapp.SuccessContract, 50)
|
|
callbackExecuted = false // callback should not be executed
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: callback calldata invalid",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d", "calldata":%d}}`, simapp.SuccessContract, 50, 50)
|
|
callbackExecuted = false // callback should not be executed
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: callback calldata hex invalid",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d", "calldata":"%s"}}`, simapp.SuccessContract, 50, "calldata")
|
|
callbackExecuted = false // callback should not be executed
|
|
},
|
|
types.CallbackTypeSendPacket,
|
|
false,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.SetupTransferTest()
|
|
|
|
transferICS4Wrapper := GetSimApp(s.chainA).TransferKeeper.GetICS4Wrapper()
|
|
|
|
packetData = transfertypes.NewFungibleTokenPacketData(
|
|
ibctesting.TestCoin.Denom,
|
|
ibctesting.TestCoin.Amount.String(),
|
|
ibctesting.TestAccAddress,
|
|
ibctesting.TestAccAddress,
|
|
fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract),
|
|
)
|
|
callbackExecuted = true
|
|
|
|
tc.malleate()
|
|
|
|
ctx := s.chainA.GetContext()
|
|
gasLimit := ctx.GasMeter().Limit()
|
|
|
|
var (
|
|
seq uint64
|
|
err error
|
|
)
|
|
sendPacket := func() {
|
|
seq, err = transferICS4Wrapper.SendPacket(ctx, s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, s.chainB.GetTimeoutHeight(), 0, packetData.GetBytes())
|
|
}
|
|
|
|
expPass := tc.expValue == nil
|
|
switch {
|
|
case expPass:
|
|
sendPacket()
|
|
s.Require().Nil(err)
|
|
s.Require().Equal(uint64(1), seq)
|
|
|
|
expEvent, exists := GetExpectedEvent(
|
|
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packetData.GetBytes(),
|
|
s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, seq, types.CallbackTypeSendPacket, nil,
|
|
)
|
|
if exists {
|
|
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
|
|
}
|
|
|
|
case tc.expPanic:
|
|
s.Require().PanicsWithValue(tc.expValue, sendPacket)
|
|
|
|
default:
|
|
sendPacket()
|
|
s.Require().ErrorIs(tc.expValue.(error), err)
|
|
s.Require().Equal(uint64(0), seq)
|
|
}
|
|
|
|
if callbackExecuted {
|
|
s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnAcknowledgementPacket() {
|
|
type expResult uint8
|
|
const (
|
|
noExecution expResult = iota
|
|
callbackFailed
|
|
callbackSuccess
|
|
)
|
|
|
|
var (
|
|
packetData transfertypes.FungibleTokenPacketData
|
|
packet channeltypes.Packet
|
|
ack []byte
|
|
ctx sdk.Context
|
|
userGasLimit uint64
|
|
)
|
|
|
|
panicError := errors.New("panic error")
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
expResult expResult
|
|
expError error
|
|
}{
|
|
{
|
|
"success",
|
|
func() {},
|
|
callbackSuccess,
|
|
nil,
|
|
},
|
|
{
|
|
"success: callback data doesn't exist",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = ""
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
nil,
|
|
},
|
|
{
|
|
"failure: underlying app OnAcknowledgePacket fails",
|
|
func() {
|
|
ack = []byte("invalid ack")
|
|
},
|
|
noExecution,
|
|
ibcerrors.ErrUnknownRequest,
|
|
},
|
|
{
|
|
"failure: no-op on callback data is not valid",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = `{"src_callback": {"address": ""}}`
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: callback execution reach out of gas, but sufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
nil,
|
|
},
|
|
{
|
|
"failure: callback execution panics on insufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit)
|
|
packet.Data = packetData.GetBytes()
|
|
|
|
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000))
|
|
},
|
|
callbackFailed,
|
|
panicError,
|
|
},
|
|
{
|
|
"failure: callback execution fails",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
nil, // execution failure in OnAcknowledgement should not block acknowledgement processing
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.SetupTransferTest()
|
|
|
|
userGasLimit = 600000
|
|
packetData = transfertypes.NewFungibleTokenPacketData(
|
|
ibctesting.TestCoin.Denom,
|
|
ibctesting.TestCoin.Amount.String(),
|
|
ibctesting.TestAccAddress,
|
|
ibctesting.TestAccAddress,
|
|
fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.SuccessContract, userGasLimit),
|
|
)
|
|
|
|
packet = channeltypes.Packet{
|
|
Sequence: 1,
|
|
SourcePort: s.path.EndpointA.ChannelConfig.PortID,
|
|
SourceChannel: s.path.EndpointA.ChannelID,
|
|
DestinationPort: s.path.EndpointB.ChannelConfig.PortID,
|
|
DestinationChannel: s.path.EndpointB.ChannelID,
|
|
Data: packetData.GetBytes(),
|
|
TimeoutHeight: s.chainB.GetTimeoutHeight(),
|
|
TimeoutTimestamp: 0,
|
|
}
|
|
|
|
ack = channeltypes.NewResultAcknowledgement([]byte{1}).Acknowledgement()
|
|
|
|
ctx = s.chainA.GetContext()
|
|
gasLimit := ctx.GasMeter().Limit()
|
|
|
|
tc.malleate()
|
|
|
|
// callbacks module is routed as top level middleware
|
|
transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName)
|
|
s.Require().True(ok)
|
|
|
|
onAcknowledgementPacket := func() error {
|
|
return transferStack.OnAcknowledgementPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, ack, s.chainA.SenderAccount.GetAddress())
|
|
}
|
|
|
|
switch tc.expError {
|
|
case nil:
|
|
err := onAcknowledgementPacket()
|
|
s.Require().Nil(err)
|
|
|
|
case panicError:
|
|
s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{
|
|
Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeAcknowledgementPacket, userGasLimit),
|
|
}, func() {
|
|
_ = onAcknowledgementPacket()
|
|
})
|
|
|
|
default:
|
|
err := onAcknowledgementPacket()
|
|
s.Require().ErrorIs(err, tc.expError)
|
|
}
|
|
|
|
sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext())
|
|
sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters
|
|
|
|
switch tc.expResult {
|
|
case noExecution:
|
|
s.Require().Len(sourceCounters, 0)
|
|
s.Require().Equal(uint8(0), sourceStatefulCounter)
|
|
|
|
case callbackFailed:
|
|
s.Require().Len(sourceCounters, 1)
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket])
|
|
s.Require().Equal(uint8(0), sourceStatefulCounter)
|
|
|
|
case callbackSuccess:
|
|
s.Require().Len(sourceCounters, 1)
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket])
|
|
s.Require().Equal(uint8(1), sourceStatefulCounter)
|
|
|
|
expEvent, exists := GetExpectedEvent(
|
|
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
|
|
packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeAcknowledgementPacket, nil,
|
|
)
|
|
s.Require().True(exists)
|
|
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnTimeoutPacket() {
|
|
type expResult uint8
|
|
const (
|
|
noExecution expResult = iota
|
|
callbackFailed
|
|
callbackSuccess
|
|
)
|
|
|
|
var (
|
|
packetData transfertypes.FungibleTokenPacketData
|
|
packet channeltypes.Packet
|
|
ctx sdk.Context
|
|
)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
expResult expResult
|
|
expValue any
|
|
}{
|
|
{
|
|
"success",
|
|
func() {},
|
|
callbackSuccess,
|
|
nil,
|
|
},
|
|
{
|
|
"success: callback data doesn't exist",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = ""
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
nil,
|
|
},
|
|
{
|
|
"failure: underlying app OnTimeoutPacket fails",
|
|
func() {
|
|
packet.Data = []byte("invalid packet data")
|
|
},
|
|
noExecution,
|
|
ibcerrors.ErrInvalidType,
|
|
},
|
|
{
|
|
"failure: no-op on callback data is not valid",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = `{"src_callback": {"address": ""}}`
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: callback execution reach out of gas, but sufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
nil,
|
|
},
|
|
{
|
|
"failure: callback execution panics on insufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.OogPanicContract)
|
|
packet.Data = packetData.GetBytes()
|
|
|
|
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000))
|
|
},
|
|
callbackFailed,
|
|
storetypes.ErrorOutOfGas{
|
|
Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeTimeoutPacket, maxCallbackGas),
|
|
},
|
|
},
|
|
{
|
|
"failure: callback execution fails",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
nil, // execution failure in OnTimeout should not block timeout processing
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.SetupTransferTest()
|
|
|
|
// NOTE: we call send packet so transfer is setup with the correct logic to
|
|
// succeed on timeout
|
|
userGasLimit := 600_000
|
|
timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().UnixNano())
|
|
msg := transfertypes.NewMsgTransfer(
|
|
s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID,
|
|
ibctesting.TestCoin, s.chainA.SenderAccount.GetAddress().String(),
|
|
s.chainB.SenderAccount.GetAddress().String(), clienttypes.ZeroHeight(), timeoutTimestamp,
|
|
fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, ibctesting.TestAccAddress, userGasLimit), // set user gas limit above panic level in mock contract keeper
|
|
)
|
|
|
|
res, err := s.chainA.SendMsgs(msg)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(res)
|
|
|
|
packet, err = ibctesting.ParseV1PacketFromEvents(res.GetEvents())
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(packet)
|
|
|
|
err = json.Unmarshal(packet.Data, &packetData)
|
|
s.Require().NoError(err)
|
|
|
|
ctx = s.chainA.GetContext()
|
|
gasLimit := ctx.GasMeter().Limit()
|
|
|
|
tc.malleate()
|
|
|
|
// callbacks module is routed as top level middleware
|
|
transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName)
|
|
s.Require().True(ok)
|
|
|
|
onTimeoutPacket := func() error {
|
|
return transferStack.OnTimeoutPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, s.chainA.SenderAccount.GetAddress())
|
|
}
|
|
|
|
switch expValue := tc.expValue.(type) {
|
|
case nil:
|
|
err := onTimeoutPacket()
|
|
s.Require().Nil(err)
|
|
case error:
|
|
err := onTimeoutPacket()
|
|
s.Require().ErrorIs(err, expValue)
|
|
default:
|
|
s.Require().PanicsWithValue(tc.expValue, func() {
|
|
_ = onTimeoutPacket()
|
|
})
|
|
}
|
|
|
|
sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext())
|
|
sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters
|
|
|
|
// account for SendPacket succeeding
|
|
switch tc.expResult {
|
|
case noExecution:
|
|
s.Require().Len(sourceCounters, 1)
|
|
s.Require().Equal(uint8(1), sourceStatefulCounter)
|
|
|
|
case callbackFailed:
|
|
s.Require().Len(sourceCounters, 2)
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket])
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket])
|
|
s.Require().Equal(uint8(1), sourceStatefulCounter)
|
|
|
|
case callbackSuccess:
|
|
s.Require().Len(sourceCounters, 2)
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket])
|
|
s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket])
|
|
s.Require().Equal(uint8(2), sourceStatefulCounter)
|
|
|
|
expEvent, exists := GetExpectedEvent(
|
|
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
|
|
packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeTimeoutPacket, nil,
|
|
)
|
|
s.Require().True(exists)
|
|
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnRecvPacket() {
|
|
type expResult uint8
|
|
const (
|
|
noExecution expResult = iota
|
|
callbackFailed
|
|
callbackSuccess
|
|
)
|
|
|
|
var (
|
|
packetData transfertypes.FungibleTokenPacketData
|
|
packet channeltypes.Packet
|
|
ctx sdk.Context
|
|
userGasLimit uint64
|
|
)
|
|
|
|
successAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
|
|
panicAck := channeltypes.NewErrorAcknowledgement(errors.New("panic"))
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
expResult expResult
|
|
expAck ibcexported.Acknowledgement
|
|
}{
|
|
{
|
|
"success",
|
|
func() {},
|
|
callbackSuccess,
|
|
successAck,
|
|
},
|
|
{
|
|
"success: callback data doesn't exist",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = ""
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
successAck,
|
|
},
|
|
{
|
|
"failure: underlying app OnRecvPacket fails",
|
|
func() {
|
|
packet.Data = []byte("invalid packet data")
|
|
},
|
|
noExecution,
|
|
channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInvalidType),
|
|
},
|
|
{
|
|
"failure: callback data is not valid",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = `{"dest_callback": {"address": ""}}`
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
noExecution,
|
|
channeltypes.NewErrorAcknowledgement(types.ErrInvalidCallbackData),
|
|
},
|
|
{
|
|
"failure: callback execution reach out of gas, but sufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
successAck,
|
|
},
|
|
{
|
|
"failure: callback execution panics on insufficient gas provided by relayer",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit)
|
|
packet.Data = packetData.GetBytes()
|
|
|
|
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000))
|
|
},
|
|
callbackFailed,
|
|
panicAck,
|
|
},
|
|
{
|
|
"failure: callback execution fails",
|
|
func() {
|
|
packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s"}}`, simapp.ErrorContract)
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
callbackFailed,
|
|
successAck,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.SetupTransferTest()
|
|
|
|
// set user gas limit above panic level in mock contract keeper
|
|
userGasLimit = 600_000
|
|
packetData = transfertypes.NewFungibleTokenPacketData(
|
|
ibctesting.TestCoin.Denom,
|
|
ibctesting.TestCoin.Amount.String(),
|
|
ibctesting.TestAccAddress,
|
|
s.chainB.SenderAccount.GetAddress().String(),
|
|
fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, ibctesting.TestAccAddress, userGasLimit),
|
|
)
|
|
|
|
packet = channeltypes.Packet{
|
|
Sequence: 1,
|
|
SourcePort: s.path.EndpointA.ChannelConfig.PortID,
|
|
SourceChannel: s.path.EndpointA.ChannelID,
|
|
DestinationPort: s.path.EndpointB.ChannelConfig.PortID,
|
|
DestinationChannel: s.path.EndpointB.ChannelID,
|
|
Data: packetData.GetBytes(),
|
|
TimeoutHeight: s.chainB.GetTimeoutHeight(),
|
|
TimeoutTimestamp: 0,
|
|
}
|
|
|
|
ctx = s.chainB.GetContext()
|
|
gasLimit := ctx.GasMeter().Limit()
|
|
|
|
tc.malleate()
|
|
|
|
// callbacks module is routed as top level middleware
|
|
transferStack, ok := s.chainB.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName)
|
|
s.Require().True(ok)
|
|
|
|
onRecvPacket := func() ibcexported.Acknowledgement {
|
|
return transferStack.OnRecvPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, s.chainB.SenderAccount.GetAddress())
|
|
}
|
|
|
|
switch tc.expAck {
|
|
case successAck:
|
|
ack := onRecvPacket()
|
|
s.Require().NotNil(ack)
|
|
|
|
case panicAck:
|
|
s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{
|
|
Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, userGasLimit),
|
|
}, func() {
|
|
_ = onRecvPacket()
|
|
})
|
|
|
|
default:
|
|
ack := onRecvPacket()
|
|
s.Require().Equal(tc.expAck, ack)
|
|
}
|
|
|
|
destStatefulCounter := GetSimApp(s.chainB).MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext())
|
|
destCounters := GetSimApp(s.chainB).MockContractKeeper.Counters
|
|
|
|
switch tc.expResult {
|
|
case noExecution:
|
|
s.Require().Len(destCounters, 0)
|
|
s.Require().Equal(uint8(0), destStatefulCounter)
|
|
|
|
case callbackFailed:
|
|
s.Require().Len(destCounters, 1)
|
|
s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket])
|
|
s.Require().Equal(uint8(0), destStatefulCounter)
|
|
|
|
case callbackSuccess:
|
|
s.Require().Len(destCounters, 1)
|
|
s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket])
|
|
s.Require().Equal(uint8(1), destStatefulCounter)
|
|
|
|
expEvent, exists := GetExpectedEvent(
|
|
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
|
|
packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil,
|
|
)
|
|
s.Require().True(exists)
|
|
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestWriteAcknowledgement() {
|
|
var (
|
|
packetData transfertypes.FungibleTokenPacketData
|
|
packet channeltypes.Packet
|
|
ctx sdk.Context
|
|
ack ibcexported.Acknowledgement
|
|
)
|
|
|
|
successAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
callbackType types.CallbackType
|
|
expError error
|
|
}{
|
|
{
|
|
"success",
|
|
func() {
|
|
ack = successAck
|
|
},
|
|
types.CallbackTypeReceivePacket,
|
|
nil,
|
|
},
|
|
{
|
|
"success: callback data doesn't exist",
|
|
func() {
|
|
//nolint:goconst
|
|
packetData.Memo = ""
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
"none", // nonexistent callback data should result in no callback execution
|
|
nil,
|
|
},
|
|
{
|
|
"failure: callback data is not valid",
|
|
func() {
|
|
packetData.Memo = `{"dest_callback": {"address": ""}}`
|
|
packet.Data = packetData.GetBytes()
|
|
},
|
|
"none", // improperly formatted callback data should result in no callback execution
|
|
types.ErrInvalidCallbackData,
|
|
},
|
|
{
|
|
"failure: ics4Wrapper WriteAcknowledgement call fails",
|
|
func() {
|
|
packet.DestinationChannel = "invalid-channel"
|
|
},
|
|
"none",
|
|
channeltypes.ErrChannelNotFound,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.SetupTransferTest()
|
|
|
|
// set user gas limit above panic level in mock contract keeper
|
|
packetData = transfertypes.NewFungibleTokenPacketData(
|
|
ibctesting.TestCoin.Denom,
|
|
ibctesting.TestCoin.Amount.String(),
|
|
ibctesting.TestAccAddress,
|
|
s.chainB.SenderAccount.GetAddress().String(),
|
|
fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"600000"}}`, ibctesting.TestAccAddress),
|
|
)
|
|
|
|
packet = channeltypes.Packet{
|
|
Sequence: 1,
|
|
SourcePort: s.path.EndpointA.ChannelConfig.PortID,
|
|
SourceChannel: s.path.EndpointA.ChannelID,
|
|
DestinationPort: s.path.EndpointB.ChannelConfig.PortID,
|
|
DestinationChannel: s.path.EndpointB.ChannelID,
|
|
Data: packetData.GetBytes(),
|
|
TimeoutHeight: s.chainB.GetTimeoutHeight(),
|
|
TimeoutTimestamp: 0,
|
|
}
|
|
|
|
ctx = s.chainB.GetContext()
|
|
gasLimit := ctx.GasMeter().Limit()
|
|
|
|
tc.malleate()
|
|
|
|
// callbacks module is routed as top level middleware
|
|
transferICS4Wrapper := GetSimApp(s.chainB).TransferKeeper.GetICS4Wrapper()
|
|
|
|
err := transferICS4Wrapper.WriteAcknowledgement(ctx, packet, ack)
|
|
|
|
expPass := tc.expError == nil
|
|
s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass)
|
|
|
|
if expPass {
|
|
s.Require().NoError(err)
|
|
|
|
expEvent, exists := GetExpectedEvent(
|
|
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
|
|
packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil,
|
|
)
|
|
if exists {
|
|
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
|
|
}
|
|
|
|
} else {
|
|
s.Require().ErrorIs(err, tc.expError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestProcessCallback() {
|
|
var (
|
|
callbackType types.CallbackType
|
|
callbackData types.CallbackData
|
|
ctx sdk.Context
|
|
callbackExecutor func(sdk.Context) error
|
|
expGasConsumed uint64
|
|
)
|
|
|
|
callbackError := errors.New("callbackExecutor error")
|
|
|
|
testCases := []struct {
|
|
name string
|
|
malleate func()
|
|
expPanic bool
|
|
expValue any
|
|
}{
|
|
{
|
|
"success",
|
|
func() {},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"success: callbackExecutor panic, but not out of gas",
|
|
func() {
|
|
callbackExecutor = func(cachedCtx sdk.Context) error {
|
|
cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption")
|
|
panic("callbackExecutor panic")
|
|
}
|
|
},
|
|
false,
|
|
errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, "callbackExecutor panic"),
|
|
},
|
|
{
|
|
"success: callbackExecutor oog panic, but retry is not allowed",
|
|
func() {
|
|
executionGas := callbackData.ExecutionGasLimit
|
|
expGasConsumed = executionGas
|
|
callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam
|
|
cachedCtx.GasMeter().ConsumeGas(expGasConsumed+1, "callbackExecutor gas consumption")
|
|
return nil
|
|
}
|
|
},
|
|
false,
|
|
errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType),
|
|
},
|
|
{
|
|
"failure: callbackExecutor error",
|
|
func() {
|
|
callbackExecutor = func(cachedCtx sdk.Context) error {
|
|
cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption")
|
|
return callbackError
|
|
}
|
|
},
|
|
false,
|
|
callbackError,
|
|
},
|
|
{
|
|
"failure: callbackExecutor panic, not out of gas, and SendPacket",
|
|
func() {
|
|
callbackType = types.CallbackTypeSendPacket
|
|
callbackExecutor = func(cachedCtx sdk.Context) error {
|
|
cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption")
|
|
panic("callbackExecutor panic")
|
|
}
|
|
},
|
|
true,
|
|
"callbackExecutor panic",
|
|
},
|
|
{
|
|
"failure: callbackExecutor oog panic, but retry is allowed",
|
|
func() {
|
|
executionGas := callbackData.ExecutionGasLimit
|
|
callbackData.CommitGasLimit = executionGas + 1
|
|
expGasConsumed = executionGas
|
|
callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam
|
|
cachedCtx.GasMeter().ConsumeGas(executionGas+1, "callbackExecutor oog panic")
|
|
return nil
|
|
}
|
|
},
|
|
true,
|
|
storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, 1000000+1)},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
s.setupChains()
|
|
|
|
// set a callback data that does not allow retry
|
|
callbackData = types.CallbackData{
|
|
CallbackAddress: s.chainB.SenderAccount.GetAddress().String(),
|
|
ExecutionGasLimit: 1_000_000,
|
|
SenderAddress: s.chainB.SenderAccount.GetAddress().String(),
|
|
CommitGasLimit: 600_000,
|
|
}
|
|
|
|
// this only makes a difference if it is SendPacket
|
|
callbackType = types.CallbackTypeReceivePacket
|
|
|
|
// expGasConsumed can be overwritten in malleate
|
|
expGasConsumed = 300_000
|
|
|
|
ctx = s.chainB.GetContext()
|
|
|
|
// set a callback executor that will always succeed after consuming expGasConsumed
|
|
callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam
|
|
cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption")
|
|
return nil
|
|
}
|
|
|
|
tc.malleate()
|
|
var err error
|
|
|
|
processCallback := func() {
|
|
err = internal.ProcessCallback(ctx, callbackType, callbackData, callbackExecutor)
|
|
}
|
|
|
|
expPass := tc.expValue == nil
|
|
switch {
|
|
case expPass:
|
|
processCallback()
|
|
s.Require().NoError(err)
|
|
case tc.expPanic:
|
|
s.Require().PanicsWithValue(tc.expValue, processCallback)
|
|
default:
|
|
processCallback()
|
|
s.Require().ErrorIs(err, tc.expValue.(error))
|
|
}
|
|
|
|
s.Require().Equal(expGasConsumed, ctx.GasMeter().GasConsumed())
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestUnmarshalPacketDataV1() {
|
|
s.setupChains()
|
|
s.path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort
|
|
s.path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort
|
|
s.path.EndpointA.ChannelConfig.Version = transfertypes.V1
|
|
s.path.EndpointB.ChannelConfig.Version = transfertypes.V1
|
|
s.path.Setup()
|
|
|
|
// We will pass the function call down the transfer stack to the transfer module
|
|
// transfer stack UnmarshalPacketData call order: callbacks -> transfer
|
|
transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName)
|
|
s.Require().True(ok)
|
|
|
|
unmarshalerStack, ok := transferStack.(types.CallbacksCompatibleModule)
|
|
s.Require().True(ok)
|
|
|
|
expPacketDataICS20V1 := transfertypes.FungibleTokenPacketData{
|
|
Denom: ibctesting.TestCoin.Denom,
|
|
Amount: ibctesting.TestCoin.Amount.String(),
|
|
Sender: ibctesting.TestAccAddress,
|
|
Receiver: ibctesting.TestAccAddress,
|
|
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address":"%s"}}`, ibctesting.TestAccAddress, ibctesting.TestAccAddress),
|
|
}
|
|
|
|
expPacketDataICS20V2 := transfertypes.InternalTransferRepresentation{
|
|
Token: transfertypes.Token{
|
|
Denom: transfertypes.NewDenom(ibctesting.TestCoin.Denom),
|
|
Amount: ibctesting.TestCoin.Amount.String(),
|
|
},
|
|
Sender: ibctesting.TestAccAddress,
|
|
Receiver: ibctesting.TestAccAddress,
|
|
Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address":"%s"}}`, ibctesting.TestAccAddress, ibctesting.TestAccAddress),
|
|
}
|
|
|
|
portID := s.path.EndpointA.ChannelConfig.PortID
|
|
channelID := s.path.EndpointA.ChannelID
|
|
|
|
// Unmarshal ICS20 v1 packet data into v2 packet data
|
|
data := expPacketDataICS20V1.GetBytes()
|
|
packetData, version, err := unmarshalerStack.UnmarshalPacketData(s.chainA.GetContext(), portID, channelID, data)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(s.path.EndpointA.ChannelConfig.Version, version)
|
|
s.Require().Equal(expPacketDataICS20V2, packetData)
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestGetAppVersion() {
|
|
s.SetupICATest()
|
|
|
|
// Obtain an IBC stack for testing. The function call will use the top of the stack which calls
|
|
// directly to the channel keeper. Calling from a further down module in the stack is not necessary
|
|
// for this test.
|
|
icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName)
|
|
s.Require().True(ok)
|
|
|
|
controllerStack, ok := icaControllerStack.(porttypes.ICS4Wrapper)
|
|
s.Require().True(ok)
|
|
appVersion, found := controllerStack.GetAppVersion(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID)
|
|
s.Require().True(found)
|
|
s.Require().Equal(s.path.EndpointA.ChannelConfig.Version, appVersion)
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnChanCloseInit() {
|
|
s.SetupICATest()
|
|
|
|
// We will pass the function call down the icacontroller stack to the icacontroller module
|
|
// icacontroller stack OnChanCloseInit call order: callbacks -> icacontroller
|
|
icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName)
|
|
s.Require().True(ok)
|
|
|
|
controllerStack, ok := icaControllerStack.(porttypes.Middleware)
|
|
s.Require().True(ok)
|
|
err := controllerStack.OnChanCloseInit(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID)
|
|
// we just check that this call is passed down to the icacontroller to return an error
|
|
s.Require().ErrorIs(err, errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "user cannot close channel"))
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnChanCloseConfirm() {
|
|
s.SetupICATest()
|
|
|
|
// We will pass the function call down the icacontroller stack to the icacontroller module
|
|
// icacontroller stack OnChanCloseConfirm call order: callbacks -> icacontroller
|
|
icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName)
|
|
s.Require().True(ok)
|
|
|
|
controllerStack, ok := icaControllerStack.(porttypes.Middleware)
|
|
s.Require().True(ok)
|
|
err := controllerStack.OnChanCloseConfirm(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID)
|
|
// we just check that this call is passed down to the icacontroller
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *CallbacksTestSuite) TestOnRecvPacketAsyncAck() {
|
|
s.setupChains()
|
|
|
|
cbs, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.MockPort)
|
|
s.Require().True(ok)
|
|
|
|
packet := channeltypes.NewPacket(
|
|
ibcmock.MockAsyncPacketData,
|
|
s.chainA.SenderAccount.GetSequence(),
|
|
s.path.EndpointA.ChannelConfig.PortID,
|
|
s.path.EndpointA.ChannelID,
|
|
s.path.EndpointB.ChannelConfig.PortID,
|
|
s.path.EndpointB.ChannelID,
|
|
clienttypes.NewHeight(0, 100),
|
|
0,
|
|
)
|
|
|
|
ack := cbs.OnRecvPacket(s.chainA.GetContext(), ibcmock.Version, packet, s.chainA.SenderAccount.GetAddress())
|
|
s.Require().Nil(ack)
|
|
s.AssertHasExecutedExpectedCallback("none", true)
|
|
}
|