mukan-ibc/modules/apps/callbacks/v2/ibc_middleware_test.go
Mukan Erkin Törük 88dd97a9f8
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
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

848 lines
24 KiB
Go

package v2_test
import (
"errors"
"fmt"
"time"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/testing/simapp"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/types"
v2 "git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/v2"
transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
channelkeeperv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/v2/keeper"
channeltypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/v2/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/core/api"
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
ibctesting "git.cw.tr/mukan-network/mukan-ibc/testing"
ibcmock "git.cw.tr/mukan-network/mukan-ibc/testing/mock"
ibcmockv2 "git.cw.tr/mukan-network/mukan-ibc/testing/mock/v2"
)
func (s *CallbacksTestSuite) TestNewIBCMiddleware() {
testCases := []struct {
name string
instantiateFn func()
expError error
}{
{
"success",
func() {
_ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas)
},
nil,
},
{
"panics with nil ics4wrapper",
func() {
_ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, nil, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas)
},
errors.New("write acknowledgement wrapper cannot be nil"),
},
{
"panics with nil underlying app",
func() {
_ = v2.NewIBCMiddleware(nil, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas)
},
fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil)),
},
{
"panics with nil contract keeper",
func() {
_ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, nil, &channelkeeperv2.Keeper{}, maxCallbackGas)
},
errors.New("contract keeper cannot be nil"),
},
{
"panics with nil channel v2 keeper",
func() {
_ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, nil, maxCallbackGas)
},
errors.New("channel keeper v2 cannot be nil"),
},
{
"panics with zero maxCallbackGas",
func() {
_ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, 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) TestWithWriteAckWrapper() {
s.setupChains()
cbsMiddleware := v2.IBCMiddleware{}
s.Require().Nil(cbsMiddleware.GetWriteAckWrapper())
cbsMiddleware.WithWriteAckWrapper(s.chainA.App.GetIBCKeeper().ChannelKeeperV2)
writeAckWrapper := cbsMiddleware.GetWriteAckWrapper()
s.Require().IsType((*channelkeeperv2.Keeper)(nil), writeAckWrapper)
}
func (s *CallbacksTestSuite) TestSendPacket() {
var packetData transfertypes.FungibleTokenPacketData
testCases := []struct {
name string
malleate func()
callbackType types.CallbackType
expPanic bool
expValue any
}{
{
"success",
func() {},
types.CallbackTypeSendPacket,
false,
nil,
},
{
"success: callback data nonexistent",
func() {
//nolint:goconst
packetData.Memo = ""
},
"none",
false,
nil,
},
{
"failure: no-op on 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: 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),
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.SetupTest()
packetData = transfertypes.NewFungibleTokenPacketData(
ibctesting.TestCoin.Denom,
ibctesting.TestCoin.Amount.String(),
s.chainA.SenderAccount.GetAddress().String(),
ibctesting.TestAccAddress,
fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract),
)
tc.malleate()
payload := channeltypesv2.NewPayload(
transfertypes.PortID, transfertypes.PortID,
transfertypes.V1, transfertypes.EncodingJSON,
packetData.GetBytes(),
)
ctx := s.chainA.GetContext()
gasLimit := ctx.GasMeter().Limit()
var err error
sendPacket := func() {
cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
err = cbs.OnSendPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID,
1, payload, s.chainA.SenderAccount.GetAddress())
}
expPass := tc.expValue == nil
switch {
case expPass:
sendPacket()
s.Require().Nil(err)
expEvent, exists := GetExpectedEvent(
ctx, packetData, gasLimit, payload.Version,
transfertypes.PortID, s.path.EndpointA.ClientID, 1, 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(err, tc.expValue.(error))
}
s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass)
})
}
}
func (s *CallbacksTestSuite) TestOnAcknowledgementPacket() {
type expResult uint8
const (
noExecution expResult = iota
callbackFailed
callbackSuccess
)
var (
packetData transfertypes.FungibleTokenPacketData
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 nonexistent",
func() {
//nolint:goconst
packetData.Memo = ""
},
noExecution,
nil,
},
{
"failure: underlying app OnAcknowledgePacket fails",
func() {
ack = []byte("invalid ack")
},
noExecution,
ibcerrors.ErrUnknownRequest,
},
{
"failure: callback data is not valid",
func() {
//nolint:goconst
packetData.Memo = `{"src_callback": {"address": ""}}`
},
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)
},
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)
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000))
},
callbackFailed,
panicError,
},
{
"failure: callback execution fails",
func() {
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract)
},
callbackFailed,
nil, // execution failure in OnAcknowledgement should not block acknowledgement processing
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.SetupTest()
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),
)
ack = channeltypes.NewResultAcknowledgement([]byte{1}).Acknowledgement()
ctx = s.chainA.GetContext()
// may malleate packetData, ack, and ctx
tc.malleate()
payload := channeltypesv2.NewPayload(
transfertypes.PortID, transfertypes.PortID,
transfertypes.V1, transfertypes.EncodingJSON,
packetData.GetBytes(),
)
gasLimit := ctx.GasMeter().Limit()
// callbacks module is routed as top level middleware
cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
onAcknowledgementPacket := func() error {
return cbs.OnAcknowledgementPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, ack, payload, 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, packetData, gasLimit, payload.Version,
payload.SourcePort, s.path.EndpointA.ClientID, 1, 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
ctx sdk.Context
)
testCases := []struct {
name string
malleate func()
expResult expResult
expValue any
}{
{
"success",
func() {},
callbackSuccess,
nil,
},
{
"success: callback data nonexistent",
func() {
//nolint:goconst
packetData.Memo = ""
},
noExecution,
nil,
},
{
"failure: underlying app OnTimeoutPacket fails",
func() {
packetData.Amount = "invalid amount"
},
noExecution,
transfertypes.ErrInvalidAmount,
},
{
"failure: callback data is not valid",
func() {
//nolint:goconst
packetData.Memo = `{"src_callback": {"address": ""}}`
},
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)
},
callbackFailed,
nil,
},
{
"failure: callback execution panics on insufficient gas provided by relayer",
func() {
packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.OogPanicContract)
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)
},
callbackFailed,
nil, // execution failure in OnTimeout should not block timeout processing
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.SetupTest()
// 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().Add(time.Second).Unix())
packetData = transfertypes.NewFungibleTokenPacketData(
ibctesting.TestCoin.Denom,
ibctesting.TestCoin.Amount.String(),
s.chainA.SenderAccount.GetAddress().String(),
ibctesting.TestAccAddress,
fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.SuccessContract, userGasLimit),
)
payload := channeltypesv2.NewPayload(
transfertypes.PortID, transfertypes.PortID,
transfertypes.V1, transfertypes.EncodingJSON,
packetData.GetBytes(),
)
packet, err := s.path.EndpointA.MsgSendPacket(timeoutTimestamp, payload)
s.Require().NoError(err)
ctx = s.chainA.GetContext()
gasLimit := ctx.GasMeter().Limit()
tc.malleate()
// update packet data in payload after malleate
payload.Value = packetData.GetBytes()
// callbacks module is routed as top level middleware
cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
onTimeoutPacket := func() error {
return cbs.OnTimeoutPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, payload, 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, packetData, gasLimit, payload.Version,
payload.SourcePort, s.path.EndpointA.ClientID, packet.Sequence, types.CallbackTypeTimeoutPacket, nil,
)
s.Require().True(exists)
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
}
})
}
}
func (s *CallbacksTestSuite) TestOnRecvPacket() {
type expResult uint8
type expRecvStatus uint8
const (
noExecution expResult = iota
callbackFailed
callbackPanic
callbackSuccess
)
const (
success expRecvStatus = iota
panics
failure
)
var (
packetData transfertypes.FungibleTokenPacketData
ctx sdk.Context
userGasLimit uint64
)
testCases := []struct {
name string
malleate func()
expResult expResult
expRecvStatus expRecvStatus
}{
{
"success",
func() {},
callbackSuccess,
success,
},
{
"success: callback data nonexistent",
func() {
//nolint:goconst
packetData.Memo = ""
},
noExecution,
success,
},
{
"failure: underlying app OnRecvPacket fails",
func() {
packetData.Denom = ""
},
noExecution,
failure,
},
{
"failure: no-op on callback data is not valid",
func() {
//nolint:goconst
packetData.Memo = `{"dest_callback": {"address": ""}}`
},
noExecution,
failure,
},
{
"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)
},
callbackFailed,
failure,
},
{
"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)
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000))
},
callbackFailed,
panics,
},
{
"failure: callback execution fails",
func() {
packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s"}}`, simapp.ErrorContract)
},
callbackFailed,
failure,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.SetupTest()
// 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),
)
payload := channeltypesv2.NewPayload(
transfertypes.PortID, transfertypes.PortID,
transfertypes.V1, transfertypes.EncodingJSON,
packetData.GetBytes(),
)
ctx = s.chainB.GetContext()
gasLimit := ctx.GasMeter().Limit()
tc.malleate()
// update packet data in payload after malleate
payload.Value = packetData.GetBytes()
// callbacks module is routed as top level middleware
cbs := s.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
onRecvPacket := func() channeltypesv2.RecvPacketResult {
return cbs.OnRecvPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, payload, s.chainB.SenderAccount.GetAddress())
}
switch tc.expRecvStatus {
case success:
recvResult := onRecvPacket()
s.Require().Equal(channeltypesv2.PacketStatus_Success, recvResult.Status)
case panics:
s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{
Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, userGasLimit),
}, func() {
_ = onRecvPacket()
})
default:
recvResult := onRecvPacket()
s.Require().Equal(channeltypesv2.PacketStatus_Failure, recvResult.Status)
}
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, packetData, gasLimit, payload.Version,
payload.DestinationPort, s.path.EndpointB.ClientID, 1, types.CallbackTypeReceivePacket, nil,
)
s.Require().True(exists)
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
}
})
}
}
func (s *CallbacksTestSuite) TestWriteAcknowledgement() {
var (
packetData transfertypes.FungibleTokenPacketData
destClient string
ctx sdk.Context
ack channeltypesv2.Acknowledgement
multiPayload bool
)
successAck := channeltypesv2.NewAcknowledgement(channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement())
testCases := []struct {
name string
malleate func()
callbackType types.CallbackType
expError error
}{
{
"success",
func() {
ack = successAck
},
types.CallbackTypeReceivePacket,
nil,
},
{
"success: callback data nonexistent",
func() {
packetData.Memo = ""
ack = successAck
},
"none",
nil,
},
{
"failure: callback data is not valid",
func() {
packetData.Memo = `{"dest_callback": {"address": ""}}`
},
"none", // improperly formatted callback data should result in no callback execution
types.ErrInvalidCallbackData,
},
{
"failure: ics4Wrapper WriteAcknowledgement call fails",
func() {
destClient = "invalid-client"
},
"none",
channeltypesv2.ErrInvalidAcknowledgement,
},
{
"failure: multipayload should fail",
func() {
multiPayload = true
},
"none",
channeltypesv2.ErrInvalidAcknowledgement,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.SetupTest()
// 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),
)
ctx = s.chainB.GetContext()
gasLimit := ctx.GasMeter().Limit()
destClient = s.path.EndpointB.ClientID
tc.malleate()
payload := channeltypesv2.NewPayload(
transfertypes.PortID, transfertypes.PortID,
transfertypes.V1, transfertypes.EncodingJSON,
packetData.GetBytes(),
)
timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().Unix())
var packet channeltypesv2.Packet
if multiPayload {
packet = channeltypesv2.NewPacket(
1, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID,
timeoutTimestamp, payload, payload,
)
} else {
packet = channeltypesv2.NewPacket(
1, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID,
timeoutTimestamp, payload,
)
}
// mock async receive manually so WriteAcknowledgement can pass
s.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(ctx, packet.DestinationClient, packet.Sequence, packet)
s.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(ctx, packet.DestinationClient, packet.Sequence)
// callbacks module is routed as top level middleware
cbs := s.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort)
mw, ok := cbs.(api.WriteAcknowledgementWrapper)
s.Require().True(ok)
err := mw.WriteAcknowledgement(ctx, destClient, packet.Sequence, ack)
expPass := tc.expError == nil
s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass)
if expPass {
s.Require().NoError(err)
expEvent, exists := GetExpectedEvent(
ctx, packetData, gasLimit, payload.Version,
payload.DestinationPort, packet.DestinationClient, packet.Sequence, types.CallbackTypeReceivePacket, nil,
)
if exists {
s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent)
}
} else {
s.Require().ErrorIs(err, tc.expError)
}
})
}
}