Skip to content

Commit

Permalink
feat(callbacks): adr8 implementation (backport #3939) (#4432)
Browse files Browse the repository at this point in the history
* feat(callbacks): adr8 implementation (#3939)

* imp(callbacks.test): added 'TestUnmarshalPacketData'

* docs(callbacks): simapp comment for callback stacks updated

* imp(callbacks.test): added 'TestOnChanCloseInit'

* fix(simapp): passed feeKeeper as channel keeper to callbacks middleware

* imp(callbacks.test): added 'TestSendPacket'

* imp(callbacks.test): added TestWriteAcknowledgement

* imp(callbacks.test): added 'TestOnChanCloseConfirm'

* imp(callbacks.test): added 'TestOnAcknowledgementPacketError'

* imp(callbacks.test): added 'TestOnTimeoutPacketError'

* imp(callbacks.test): added export_test.go

* imp(callbacks.test): added 'TestProcessCallbackDataGetterError'

* imp(callbacks.test): added events tests

* imp(callbacks): using PacketDataUnmarshaler to unmarshal instead of the full app

* imp(callbacks): updated the api of getCallbackData functions

* imp(callbacks): added TestGetSourceCallbackDataTransfer and TestGetDestCallbackDataTransfer

* imp(callbacks.test): added export_test for type tests

* imp(callbacks.test): added 'TestGetCallbackDataErrors'

* imp(fee_test): added 'TestUnmarshalPacketDataError'

* style(adr8): renamed the contract api functions

* feat(callbacks.test): added incentivized transfer tests

* imp(callbacks_test): added timeout test case to fee test

* style(ica.adr8): updated godocs

* style(ica.adr8): updated godocs

* feat(adr8): replaced PacketDataUnmarshaller with PacketInfoProvider

* imp(callbacks): added sender and receiver addresses to ContractKeeper interface

* docs(ica): godocs updated

* style(adr8): renamed PacketDataUnmarshaler to PacketInfoProvider

* style(callbacks): renamed channel to ics4Wrapper

* docs(callbacks): updated godocs

* imp(adr8_test): tested new GetPacketSender and GetPacketReceiver interface functions

* feat(adr8): added IBCSendPacketCallback to ContractKeeper

* imp(testing/mock): added callback counter helpers

* imp(ica, transfer): added WithICS4Wrapper api function

* feat(callbacks_test): SendPacket tests are now passing

* imp(fee_test): added more tests to TestPacketInfoProviderInterfaceError

* style(callbacks): renamed PacketUnmarshalerIBCModule to PacketInfoProviderIBCModule

* feat(callbacks): added maxGas param to middleware

* fix(callbacks): fixed SendPacket

* feat(callbacks): implemented WriteAcknowledgement callbacks

* style(mock): updated the name of callback counter

* fix(callbacks): fixed using channelID instead of portID

* feat(callbacks): all acknowledgements implemented

* style(ica.adr8): used more consistent formating in ica and transfer

* docs(ica, transfer): updated 'WithICS4Wrapper's godocs

* imp(callbacks_test): improved WriteAcknowledgement tests

* tests(mock, callbacks): moved mock PacketUnmarshaller logic to mock module

* tests(callbacks): added mock async ack test

* ci: CODEOWNERS updated to include callbacks

* docs(callbacks_test): updated godocs

* imp(callbacks): only handling oog panic in recovery now

* style(callbacks): fix variable name

* imp(mock): mock panic is now a real oog panic

* tests(adr8): added state reversal test

* style(callbacks): renamed hasEnoughGas to remainingGasIsAtGasLimit

* tests(adr8): moved panic and error treshold to 400k and 500k gas respectively

* fix(callbacks): fixed panic handling

* imp(callbacks_test): added a low relayer gas test

* fix(transfer_test.adr8): fixed a typo in a test case

* docs(callbacks): improved godocs

* imp(callbacks): added CommitGasLimit to CallbackData for events

* imp(callbacks): AttributeKeyCallbackCommitGasLimit added to events

* fix(callbacks): fixed major gas panic issue

* imp(callbacks_test): improved the oog panic test

* style(callbacks): used '.GetData()' instead of '.Data'

* docs(adr8): updated some godocs

* style(ica/host_test): fixed test case memo styling

* style(ica/controller): docs and style fixes

* imp(ica/host): adr8 removed from icahost

* docs(adr8): updated godocs for withics4wrapper

* docs(adr8): updated godocs for gasLimit specs

* style(adr8_test): fixed test case naming

* docs(adr8): updated godocs for some interface functions

* style(fee.adr8): renamed unmarshaler to provider in some cases

* imp(adr8_test): improved mock unmarshaler

* docs(transfer.adr8): updated godocs

* docs(adr8): updated godocs

* docs(adr8): updated godocs

* docs(callbacks): updated godocs

* style(callbacks): moved SendPacket func to top

* docs(callbacks): updated godocs

* imp(callbacks): logging to debug instead of info

* style(callbacks): renamed remainingGasIsAtGasLimit -> commitTxIfOutOfGas

* docs(callbacks): updated godocs

* docs(callbacks): updated event docs

* fix(callbacks): added CallbackTypeSendPacket to events

* imp(callbacks): events emit port and channel id based on src vs dest

* docs(callbacks): updated godocs

* imp(callbacks): changed some event to a log

* imp(callbacks): improved log

* docs(callbacks): updated godocs

* imp(callbacks): unsuccessful ack now bypasses callback in 'OnRecvPacket'

* imp(callbacks_test): added mock logger

* imp(mock): created mock logger

* style: ran 'golangci-lint run --fix'

* style(callbacks): made code more concise

* style(callbacks): renamed PacketInfoProviderIBCModule to CallbacksCompatibleModule

* style(callbacks): improved they style of getCallbackData and negated the bool for better readability

* style(callbacks): used constants for 'success' and 'failure' attributes

* docs(adr8): updated godocs

* style(ica/controller): added more explicit prefix check

* imp(adr8): moved 'GetPacketSender' and 'GetPacketReceiver' to 'CallbackPacketData' interface

* style(adr8): renamed PacketInfoProvider to PacketDataUnmarshaler

* imp(callbacks_test): switched hostStack for controllerStack

* imp(callbacks_test): added missing test case

* imp(callbacks): callbacks can now reject SendPacket

* imp(callbacks_test): added TestSendPacketReject

* style(callbacks_test): using TestCoin instead

* imp(callbacks_test): added TestWriteAcknowledgementOogError and TestOnTimeoutPacketOogError

* imp(callbacks_test): added TestOnAcknowledgementPacketOogError

* imp(adr8): removed packetReceiver concept

* imp(adr8): removed srcChannelID from GetPacketSender interface

* imp(callbacks): oogError is now simply oogPanic

* imp(callbacks): added more mw initialization notnil checks

* docs(callbacks): updated godocs

* feat(adr8): moved adr8 logic to callbacks middleware

* style(callbacks): replaced AuthAddr -> SenderAddr

* imp(callbacks_test): increased codecov

* docs(adr8): improved some godocs around AdditionalPacketDataProvider interface

* revert(docs): reverted changes to adr8 specs, this needs a seperate PR

* imp(callbacks_test): improved tests slightly

* docs(callbacks): improved godocs for keys.go

* docs(mock.adr8): updated godocs for mock logger

* imp(adr8): changed GetAdditionalData function signature

* imp(callbacks): split AdditionalPacketDataProvider into two interfaces

* style(mock): used interface impl convention

* tests: removed ErrorMock

* style(callbacks_test): improved test style

* style(callbacks_test): improved test name

* style(core, apps): renamed PacketSenderRetriever to PacketData

* style(adr8): conforming to revive linter

* fix(transfer_test, ica/controller_test): fixed failing tests

* style: conforming to revive linter

* nit(ica): removed uneeded diffs

* style(callbacks): some style updates

* docs(callbacks): updated godocs

* imp(callbacks): added 'WithICS4Wrapper'

* style(callbacks): rename GasLimit -> ExecutionGasLimit

* imp(callbacks): allowRetry was removed

* style(callbacks): moved callbackAddr code block above gas logic

* style(callbacks): renamed ContractAddr,SenderAddr ->
ContractAddress,SenderAddress

* style(callbacks): updated godocs and var names for keys and errors

* docs(callbacks): updated godocs for contract keeper

* test: remove unnecessary code

* test: apply review code suggestions

* test: move SendPacket test up in layout

* refactor: panic with errors in NewIBCMiddleware

* test: refactor TestWithICS4Wrapper to simplify and reduce  LOC

* chore: rm mock logger and usage in tests

* nit: explicitly return 0 sequence when error is not nil

* lint: make use of unused test var maxCallbackGas

* imp(callbacks): reduced case logic for eventTrigger

* style(callbacks): improved event keys

* refactor(callbacks): refactored gas logic

* imp(callbacks): empty address returns an error now

* style(callbacks): styled function arguments

* refactor: simplify testing setup for callbacks

* rename: mock's keeper.go to contract_keeper.go

* refactor: remove mock keeper, use only mock contract keeper

* rename: StateCounter -> StateEntryCounter

* nit: simapp in-code docs

* refactor: simplify mock contract keeper process callback

* test: remove unnecessary test cases in transfer/fee integration tests

* imp(callbacks): moved 'callbackDataGetter' logic up a level

* refactor(callbacks): moved emit event logic up a level

* style(callbacks): styled function arguments

* docs(callbacks): improved godocs of contract keeper

* style: renamed CallbackTypeAcknowledgement -> CallbackTypeAcknowledgementPacket

* docs(callbacks): fixed events godocs

* style(callbacks_test): fixed typo

* style(callbacks): rename timeout -> timeout_packet

* style(callbacks): rename ContractAddress -> CallbackAddress

* imp(callbacks): don't handle panics for SendPacket

* style: renamed CallbackTypeWriteAcknowledgement -> CallbackTypeReceivePacket

* style: renamed CallbackType -> CallbackTrigger

* style(callbacks_test): fixed typo in test case

* docs(mock.adr8): updated godocs of contract keeper

* imp(callbacks): moved logging after possible retry

* style(callbacks): renamed function argument callbackType -> callbackTrigger

* imp(callbacks): fixed logger name

* imp(callbacks): 'LogDebugWithPacket' added

* refactor(ibc_middleware_test): turn SendPacket into table test

* test: add test cases for SendPacket table test

* refactor(ibc_middleware_test): turn OnAcknowledgementPacket tests into table tests

* test(OnAcknowledgement): add counter and state entry checks

* test(ica_test): remove duplicate tests

Remove tests which relied on older assertion in mock contract keeper
Tests for contract execution failure can be added with issue #4390

* testing: fix usage on TimeoutPacket to use counterparty portID/channelID in nextSeqRecv query

* test(ica_test): simplify timeout logic

* test(types/callback_test.go): remove unused testing bool

* style: ran golangci-lint

* style: renamed CallbackTrigger -> CallbackType

* refactor(ibc_middleware_test): turn OnTimeout into a table test

* refactor(ibc_middleware_test): turn OnRecvPacket into a table test

* style(callbacks_test): added nolint comments

* style(callbacks_test): fixed some typos

* imp(callbacks_test): added table tests for WriteAcknowledgement'

* imp(callbacks_test): removed dest_callback test cases for ica as it is not supported

* fix(callbacks): fixed send_packet panic handling

* fix(callbacks_test): fix failing tests due to SendPacket panic

* imp(callbacks): added 'AllowRetry' function

* imp(callbacks): processCallback panic recovery logic is simplified

* style(callbacks): updated style of the comment

* fix(callbacks_test): removed potential premature return

* docs(callbacks_test): updated inline comment

* imp(callbacks_test): 'TestProcessCallback' added

* imp(callbacks): upgraded the panic on timeout logic

* docs(callbacks): added inline comments

* imp(callbacks): removed 'LogDebugWithPacket'

* docs(callbacks): updated godocs and inline comments

* imp(callbacks): prevent maxCallbackGas from being 0

* imp(callbacks): removed logger

* imp(callbacks): added 'ErrCannotUnmarshalPacketData'

* imp(callbacks_test): created ''TestGetCallbackData

* imp(callbacks_test): improved 'TestNewIBCMiddleware'

* imp(callbacks): issue#4323 - add strings.Trimspace

* docs(callbacks): issue#4325 - inline comment added for explaining why nil is returned on error

* style(callbacks): passing nil instead of err to events in SendPacket

* docs(callbacks): issue#4325 - added inline comments explaining why some error are only used for event emissions

* imp(callbacks_test): added test cases for '0' user defined gas limit

* imp(simapp): removed unneeded comment

* imp(callbacks_test): using testAccAddress for transfer tests now

---------

Co-authored-by: colin axner <25233464+colin-axner@users.noreply.github.com>
Co-authored-by: Damian Nolan <damiannolan@gmail.com>
(cherry picked from commit 2c11494)

# Conflicts:
#	.github/CODEOWNERS
#	modules/apps/29-fee/ibc_middleware_test.go

* fix: fixed merge conflicts and downstreamed testing package bugfixes

* fix: CODEOWNERS conflicts resolved

* imp(callbacks): backported removal of reconstructed packet

* test(callbacks): removed a testcase that requires testing package improvements to be downstreamed

* revert: revert simapp api breaking changes

* fix: fixed tests with SendMsg override

---------

Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com>
Co-authored-by: srdtrk <srdtrk@hotmail.com>
  • Loading branch information
3 people committed Aug 24, 2023
1 parent e3faf70 commit 23b4a24
Show file tree
Hide file tree
Showing 24 changed files with 3,701 additions and 23 deletions.
6 changes: 5 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@
/proto/ibc/applications/fee/ @AdityaSripal @charleenfei @colin-axner @damiannolan

# CODEOWNERS for docs
/docs/ @colin-axner @AdityaSripal @crodriguezvega @charleenfei @damiannolan @chatton @tmsdkeys
/docs/ @colin-axner @AdityaSripal @crodriguezvega @charleenfei @damiannolan @chatton @DimitrisJim @srdtrk

# CODEOWNERS for callbacks middleware

/modules/apps/callbacks/ @colin-axner @AdityaSripal @damiannolan @srdtrk
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
controllerkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/keeper"
"github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
fee "github.com/cosmos/ibc-go/v7/modules/apps/29-fee"
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types"
host "github.com/cosmos/ibc-go/v7/modules/core/24-host"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
)
Expand Down Expand Up @@ -839,7 +839,7 @@ func (suite *InterchainAccountsTestSuite) TestGetAppVersion() {
cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
suite.Require().True(ok)

controllerStack := cbs.(fee.IBCMiddleware)
controllerStack := cbs.(porttypes.Middleware)
appVersion, found := controllerStack.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
suite.Require().True(found)
suite.Require().Equal(path.EndpointA.ChannelConfig.Version, appVersion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

genesistypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/genesis/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
ibcfeekeeper "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/keeper"
channelkeeper "github.com/cosmos/ibc-go/v7/modules/core/04-channel/keeper"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
Expand Down Expand Up @@ -261,11 +260,9 @@ func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() {
func (suite *KeeperTestSuite) TestWithICS4Wrapper() {
suite.SetupTest()

// test if the ics4 wrapper is the fee keeper initially
// test if the ics4 wrapper is the channel keeper initially
ics4Wrapper := suite.chainA.GetSimApp().ICAControllerKeeper.GetICS4Wrapper()

_, isFeeKeeper := ics4Wrapper.(ibcfeekeeper.Keeper)
suite.Require().True(isFeeKeeper)
_, isChannelKeeper := ics4Wrapper.(channelkeeper.Keeper)
suite.Require().False(isChannelKeeper)

Expand All @@ -275,6 +272,4 @@ func (suite *KeeperTestSuite) TestWithICS4Wrapper() {

_, isChannelKeeper = ics4Wrapper.(channelkeeper.Keeper)
suite.Require().True(isChannelKeeper)
_, isFeeKeeper = ics4Wrapper.(ibcfeekeeper.Keeper)
suite.Require().False(isFeeKeeper)
}
2 changes: 1 addition & 1 deletion modules/apps/29-fee/ibc_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ func (suite *FeeTestSuite) TestGetAppVersion() {
cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module)
suite.Require().True(ok)

feeModule := cbs.(fee.IBCMiddleware)
feeModule := cbs.(porttypes.Middleware)

appVersion, found := feeModule.GetAppVersion(suite.chainA.GetContext(), portID, channelID)

Expand Down
301 changes: 301 additions & 0 deletions modules/apps/callbacks/callbacks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
package ibccallbacks_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/suite"

sdkmath "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
feetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types"
"github.com/cosmos/ibc-go/v7/modules/apps/callbacks/types"
transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
ibctesting "github.com/cosmos/ibc-go/v7/testing"
ibcmock "github.com/cosmos/ibc-go/v7/testing/mock"
simapp "github.com/cosmos/ibc-go/v7/testing/simapp"
)

const maxCallbackGas = uint64(1000000)

// CallbacksTestSuite defines the needed instances and methods to test callbacks
type CallbacksTestSuite struct {
suite.Suite

coordinator *ibctesting.Coordinator

chainA *ibctesting.TestChain
chainB *ibctesting.TestChain

path *ibctesting.Path
}

// setupChains sets up a coordinator with 2 test chains.
func (s *CallbacksTestSuite) setupChains() {
s.coordinator = ibctesting.NewCoordinator(s.T(), 2)
s.chainA = s.coordinator.GetChain(ibctesting.GetChainID(1))
s.chainB = s.coordinator.GetChain(ibctesting.GetChainID(2))
s.path = ibctesting.NewPath(s.chainA, s.chainB)
}

// SetupTransferTest sets up a transfer channel between chainA and chainB
func (s *CallbacksTestSuite) SetupTransferTest() {
s.setupChains()

s.path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort
s.path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort
s.path.EndpointA.ChannelConfig.Version = transfertypes.Version
s.path.EndpointB.ChannelConfig.Version = transfertypes.Version

s.coordinator.Setup(s.path)
}

// SetupFeeTransferTest sets up a fee middleware enabled transfer channel between chainA and chainB
func (s *CallbacksTestSuite) SetupFeeTransferTest() {
s.setupChains()

feeTransferVersion := string(feetypes.ModuleCdc.MustMarshalJSON(&feetypes.Metadata{FeeVersion: feetypes.Version, AppVersion: transfertypes.Version}))
s.path.EndpointA.ChannelConfig.Version = feeTransferVersion
s.path.EndpointB.ChannelConfig.Version = feeTransferVersion
s.path.EndpointA.ChannelConfig.PortID = transfertypes.PortID
s.path.EndpointB.ChannelConfig.PortID = transfertypes.PortID

s.coordinator.Setup(s.path)
}

func (s *CallbacksTestSuite) SetupMockFeeTest() {
s.setupChains()

mockFeeVersion := string(feetypes.ModuleCdc.MustMarshalJSON(&feetypes.Metadata{FeeVersion: feetypes.Version, AppVersion: ibcmock.Version}))
s.path.EndpointA.ChannelConfig.Version = mockFeeVersion
s.path.EndpointB.ChannelConfig.Version = mockFeeVersion
s.path.EndpointA.ChannelConfig.PortID = ibctesting.MockFeePort
s.path.EndpointB.ChannelConfig.PortID = ibctesting.MockFeePort
}

// SetupICATest sets up an interchain accounts channel between chainA (controller) and chainB (host).
// It funds and returns the interchain account address owned by chainA's SenderAccount.
func (s *CallbacksTestSuite) SetupICATest() string {
s.setupChains()
s.coordinator.SetupConnections(s.path)

icaOwner := s.chainA.SenderAccount.GetAddress().String()
// ICAVersion defines a interchain accounts version string
icaVersion := icatypes.NewDefaultMetadataString(s.path.EndpointA.ConnectionID, s.path.EndpointB.ConnectionID)
icaControllerPortID, err := icatypes.NewControllerPortID(icaOwner)
s.Require().NoError(err)

s.path.SetChannelOrdered()
s.path.EndpointA.ChannelConfig.PortID = icaControllerPortID
s.path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID
s.path.EndpointA.ChannelConfig.Version = icaVersion
s.path.EndpointB.ChannelConfig.Version = icaVersion

s.RegisterInterchainAccount(icaOwner)
// open chan init must be skipped. So we cannot use .CreateChannels()
err = s.path.EndpointB.ChanOpenTry()
s.Require().NoError(err)
err = s.path.EndpointA.ChanOpenAck()
s.Require().NoError(err)
err = s.path.EndpointB.ChanOpenConfirm()
s.Require().NoError(err)

interchainAccountAddr, found := s.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(s.chainB.GetContext(), s.path.EndpointA.ConnectionID, s.path.EndpointA.ChannelConfig.PortID)
s.Require().True(found)

// fund the interchain account on chainB
msgBankSend := &banktypes.MsgSend{
FromAddress: s.chainB.SenderAccount.GetAddress().String(),
ToAddress: interchainAccountAddr,
Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000))),
}
res, err := s.chainB.SendMsgs(msgBankSend)
s.Require().NotEmpty(res)
s.Require().NoError(err)

return interchainAccountAddr
}

// RegisterInterchainAccount submits a MsgRegisterInterchainAccount and updates the controller endpoint with the
// channel created.
func (s *CallbacksTestSuite) RegisterInterchainAccount(owner string) {
msgRegister := icacontrollertypes.NewMsgRegisterInterchainAccount(s.path.EndpointA.ConnectionID, owner, s.path.EndpointA.ChannelConfig.Version)

res, err := s.chainA.SendMsgs(msgRegister)
s.Require().NotEmpty(res)
s.Require().NoError(err)

channelID, err := ibctesting.ParseChannelIDFromEvents(res.GetEvents())
s.Require().NoError(err)

s.path.EndpointA.ChannelID = channelID
}

// AssertHasExecutedExpectedCallback checks the stateful entries and counters based on callbacktype.
// It assumes that the source chain is chainA and the destination chain is chainB.
func (s *CallbacksTestSuite) AssertHasExecutedExpectedCallback(callbackType types.CallbackType, expSuccess bool) {
var expStatefulEntries uint8
if expSuccess {
// if the callback is expected to be successful,
// we expect at least one state entry
expStatefulEntries = 1
}

sourceStatefulCounter := s.chainA.GetSimApp().MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext())
destStatefulCounter := s.chainB.GetSimApp().MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext())

switch callbackType {
case "none":
s.Require().Equal(uint8(0), sourceStatefulCounter)
s.Require().Equal(uint8(0), destStatefulCounter)

case types.CallbackTypeSendPacket:
s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source send packet callback")
s.Require().Equal(uint8(0), destStatefulCounter)

case types.CallbackTypeAcknowledgementPacket, types.CallbackTypeTimeoutPacket:
expStatefulEntries *= 2 // expect OnAcknowledgement/OnTimeout to be successful as well as the initial SendPacket
s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source acknowledgement/timeout callbacks")
s.Require().Equal(uint8(0), destStatefulCounter)

case types.CallbackTypeReceivePacket:
s.Require().Equal(uint8(0), sourceStatefulCounter)
s.Require().Equal(expStatefulEntries, destStatefulCounter)

default:
s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType))
}

s.AssertCallbackCounters(callbackType)
}

func (s *CallbacksTestSuite) AssertCallbackCounters(callbackType types.CallbackType) {
sourceCounters := s.chainA.GetSimApp().MockContractKeeper.Counters
destCounters := s.chainB.GetSimApp().MockContractKeeper.Counters

switch callbackType {
case "none":
s.Require().Len(sourceCounters, 0)
s.Require().Len(destCounters, 0)

case types.CallbackTypeSendPacket:
s.Require().Len(sourceCounters, 1)
s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket])

case types.CallbackTypeAcknowledgementPacket:
s.Require().Len(sourceCounters, 2)
s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket])
s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket])

s.Require().Len(destCounters, 0)

case types.CallbackTypeReceivePacket:
s.Require().Len(sourceCounters, 0)
s.Require().Len(destCounters, 1)
s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket])

case types.CallbackTypeTimeoutPacket:
s.Require().Len(sourceCounters, 2)
s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket])
s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket])

s.Require().Len(destCounters, 0)

default:
s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType))
}
}

func TestIBCCallbacksTestSuite(t *testing.T) {
suite.Run(t, new(CallbacksTestSuite))
}

// AssertHasExecutedExpectedCallbackWithFee checks if only the expected type of callback has been executed
// and that the expected ics-29 fee has been paid.
func (s *CallbacksTestSuite) AssertHasExecutedExpectedCallbackWithFee(
callbackType types.CallbackType, isSuccessful bool, isTimeout bool,
originalSenderBalance sdk.Coins, fee feetypes.Fee,
) {
// Recall that:
// - the source chain is chainA
// - forward relayer is chainB.SenderAccount
// - reverse relayer is chainA.SenderAccount
// - The counterparty payee of the forward relayer in chainA is chainB.SenderAccount (as a chainA account)

// We only check if the fee is paid if the callback is successful.
if !isTimeout && isSuccessful {
// check forward relay balance
s.Require().Equal(
fee.RecvFee,
sdk.NewCoins(s.chainA.GetSimApp().BankKeeper.GetBalance(s.chainA.GetContext(), s.chainB.SenderAccount.GetAddress(), ibctesting.TestCoin.Denom)),
)

s.Require().Equal(
fee.AckFee.Add(fee.TimeoutFee...), // ack fee paid, timeout fee refunded
sdk.NewCoins(
s.chainA.GetSimApp().BankKeeper.GetBalance(
s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(),
ibctesting.TestCoin.Denom),
).Sub(originalSenderBalance[0]),
)
} else if isSuccessful {
// forward relay balance should be 0
s.Require().Equal(
sdk.NewCoin(ibctesting.TestCoin.Denom, sdkmath.ZeroInt()),
s.chainA.GetSimApp().BankKeeper.GetBalance(s.chainA.GetContext(), s.chainB.SenderAccount.GetAddress(), ibctesting.TestCoin.Denom),
)

// all fees should be returned as sender is the reverse relayer
s.Require().Equal(
fee.Total(),
sdk.NewCoins(
s.chainA.GetSimApp().BankKeeper.GetBalance(
s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(),
ibctesting.TestCoin.Denom),
).Sub(originalSenderBalance[0]),
)
}
s.AssertHasExecutedExpectedCallback(callbackType, isSuccessful)
}

// OverrideSendMsgWithAssertion overrides both chains' SendMsgsOverride function to assert whether the
// transaction is successful or not.
func OverrideSendMsgWithAssertion(chain *ibctesting.TestChain, expPass bool) {
chain.SendMsgsOverride = func(msgs ...sdk.Msg) (*sdk.Result, error) {
// ensure the chain has the latest time
chain.Coordinator.UpdateTimeForChain(chain)

_, r, err := simapp.SignAndDeliver(
chain.T,
chain.TxConfig,
chain.App.GetBaseApp(),
chain.GetContext().BlockHeader(),
msgs,
chain.ChainID,
[]uint64{chain.SenderAccount.GetAccountNumber()},
[]uint64{chain.SenderAccount.GetSequence()},
true, expPass, chain.SenderPrivKey,
)
if err != nil {
return nil, err
}

// NextBlock calls app.Commit()
chain.NextBlock()

// increment sequence for successful transaction execution
err = chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1)
if err != nil {
return nil, err
}

chain.Coordinator.IncrementTime()

return r, nil
}
}
25 changes: 25 additions & 0 deletions modules/apps/callbacks/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ibccallbacks

/*
This file is to allow for unexported functions and fields to be accessible to the testing package.
*/

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ibc-go/v7/modules/apps/callbacks/types"
porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types"
)

// ProcessCallback is a wrapper around processCallback to allow the function to be directly called in tests.
func (im IBCMiddleware) ProcessCallback(
ctx sdk.Context, callbackType types.CallbackType,
callbackData types.CallbackData, callbackExecutor func(sdk.Context) error,
) error {
return im.processCallback(ctx, callbackType, callbackData, callbackExecutor)
}

// GetICS4Wrapper is a getter for the IBCMiddleware's ICS4Wrapper.
func (im *IBCMiddleware) GetICS4Wrapper() porttypes.ICS4Wrapper {
return im.ics4Wrapper
}
Loading

0 comments on commit 23b4a24

Please sign in to comment.