diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e5ba7bac..c55f1b7f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changelog +## [v12.0.0](https://github.com/Stride-Labs/stride/releases/tag/v12.0.0) - 2023-07-15 + +### On-Chain changes +1. Add code required for ICS-consumer-migration ([#811](https://github.com/Stride-Labs/stride/pull/811)) + +### Off-Chain changes +1. Add helper scripts and tester files for ICS-consumer-migration ([#811](https://github.com/Stride-Labs/stride/pull/811)) + ## [v11.0.0](https://github.com/Stride-Labs/stride/releases/tag/v11.0.0) - 2023-06-23 ### On-Chain changes diff --git a/app/app.go b/app/app.go index f8ac680b88..c51f18dec4 100644 --- a/app/app.go +++ b/app/app.go @@ -576,6 +576,7 @@ func NewStrideApp( ) stakeibcModule := stakeibcmodule.NewAppModule(appCodec, app.StakeibcKeeper, app.AccountKeeper, app.BankKeeper) + stakeibcIBCModule := stakeibcmodule.NewIBCModule(app.StakeibcKeeper) app.AutopilotKeeper = *autopilotkeeper.NewKeeper( appCodec, @@ -618,13 +619,19 @@ func NewStrideApp( epochsModule := epochsmodule.NewAppModule(appCodec, app.EpochsKeeper) icacallbacksModule := icacallbacksmodule.NewAppModule(appCodec, app.IcacallbacksKeeper, app.AccountKeeper, app.BankKeeper) - icacallbacksIBCModule := icacallbacksmodule.NewIBCModule(app.IcacallbacksKeeper) - // Register IBC calllbacks - if err := app.IcacallbacksKeeper.SetICACallbacks( - app.StakeibcKeeper.Callbacks(), - app.RecordsKeeper.Callbacks(), - ); err != nil { + // Register ICA calllbacks + // NOTE: The icacallbacks struct implemented below provides a mapping from ICA channel owner to ICACallback handler, + // where the callback handler stores and routes to the various callback functions for a particular module. + // However, as of ibc-go v6, the icacontroller module owns the ICA channel. A consequence of this is that there can + // be no more than one module that implements ICA callbacks. Should we add an new module with ICA support in the future, + // we'll need to refactor this + err = app.IcacallbacksKeeper.SetICACallbackHandler(icacontrollertypes.SubModuleName, app.StakeibcKeeper.ICACallbackHandler()) + if err != nil { + return nil + } + err = app.IcacallbacksKeeper.SetICACallbackHandler(ibctransfertypes.ModuleName, app.RecordsKeeper.ICACallbackHandler()) + if err != nil { return nil } @@ -641,7 +648,6 @@ func NewStrideApp( app.MsgServiceRouter(), ) icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) - // Create the middleware stacks // Stack one (ICAHost Stack) contains: // - IBC @@ -649,15 +655,13 @@ func NewStrideApp( // - base app icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) - // Stack two (ICACallbacks Stack) contains + // Stack two (Stakeibc Stack) contains // - IBC // - ICA // - stakeibc - // - ICACallbacks // - base app - var icacallbacksStack porttypes.IBCModule = icacallbacksIBCModule - icacallbacksStack = stakeibcmodule.NewIBCMiddleware(icacallbacksStack, app.StakeibcKeeper) - icacallbacksStack = icacontroller.NewIBCMiddleware(icacallbacksStack, app.ICAControllerKeeper) + var stakeibcStack porttypes.IBCModule = stakeibcIBCModule + stakeibcStack = icacontroller.NewIBCMiddleware(stakeibcStack, app.ICAControllerKeeper) // Stack three contains // - IBC @@ -672,12 +676,20 @@ func NewStrideApp( transferStack = autopilot.NewIBCModule(app.AutopilotKeeper, transferStack) // Create static IBC router, add transfer route, then set and seal it + // Two routes are included for the ICAController because of the following procedure when registering an ICA + // 1. RegisterInterchainAccount binds the new portId to the icacontroller module and initiates a channel opening + // 2. MsgChanOpenInit is invoked from the IBC message server. The message server identifies that the + // icacontroller module owns the portID and routes to the stakeibc stack (the "icacontroller" route below) + // 3. The stakeibc stack works top-down, first in the ICAController's OnChanOpenInit, and then in stakeibc's OnChanOpenInit + // 4. In stakeibc's OnChanOpenInit, the stakeibc module steals the portId from the icacontroller module + // 5. Now in OnChanOpenAck and any other subsequent IBC callback, the message server will identify + // the portID owner as stakeibc and route to the same stakeibcStack, this time using the "stakeibc" route instead ibcRouter := porttypes.NewRouter() ibcRouter. // ICAHost Stack AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). - // ICACallbacks Stack - AddRoute(icacontrollertypes.SubModuleName, icacallbacksStack). + // Stakeibc Stack + AddRoute(icacontrollertypes.SubModuleName, stakeibcStack). // Transfer stack AddRoute(ibctransfertypes.ModuleName, transferStack). // Consumer stack diff --git a/dockernet/config.sh b/dockernet/config.sh index 6e74fd96a8..e8e257a8a4 100755 --- a/dockernet/config.sh +++ b/dockernet/config.sh @@ -157,7 +157,7 @@ STRIDE_MAIN_CMD="$STRIDE_BINARY --home $DOCKERNET_HOME/state/${STRIDE_NODE_PREFI # GAIA GAIA_CHAIN_ID=GAIA GAIA_NODE_PREFIX=gaia -GAIA_NUM_NODES=1 +GAIA_NUM_NODES=3 GAIA_BINARY="$DOCKERNET_HOME/../build/gaiad" GAIA_VAL_PREFIX=gval GAIA_REV_ACCT=grev1 diff --git a/x/icacallbacks/ibc_module.go b/x/icacallbacks/ibc_module.go deleted file mode 100644 index bfb17cf5cd..0000000000 --- a/x/icacallbacks/ibc_module.go +++ /dev/null @@ -1,173 +0,0 @@ -package icacallbacks - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/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" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - - "github.com/Stride-Labs/stride/v12/x/icacallbacks/keeper" - "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" -) - -var _ porttypes.IBCModule = &IBCModule{} - -type IBCModule struct { - keeper keeper.Keeper -} - -func NewIBCModule(k keeper.Keeper) IBCModule { - return IBCModule{ - keeper: k, - } -} - -// No custom logic is necessary in OnChanOpenInit -func (im IBCModule) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - channelCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) (string, error) { - return version, nil -} - -// OnChanOpenTry should not be executed in the ICA stack -func (im IBCModule) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - counterpartyVersion string, -) (string, error) { - panic("UNIMPLEMENTED") -} - -// No custom logic is necessary in OnChanOpenAck -func (im IBCModule) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - counterpartyChannelID string, - counterpartyVersion string, -) error { - return nil -} - -// OnChanOpenConfirm should not be executed in the ICA stack -func (im IBCModule) OnChanOpenConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - panic("UNIMPLEMENTED") -} - -// OnChanCloseInit should not be executed in the ICA stack -func (im IBCModule) OnChanCloseInit( - ctx sdk.Context, - portID, - channelID string, -) error { - panic("UNIMPLEMENTED") -} - -// No custom logic is necessary in OnChanCloseConfirm -func (im IBCModule) OnChanCloseConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return nil -} - -// OnChanOpenAck routes the packet to the relevant callback function -func (im IBCModule) OnAcknowledgementPacket( - ctx sdk.Context, - modulePacket channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - im.keeper.Logger(ctx).Info(fmt.Sprintf("OnAcknowledgementPacket (ICACallbacks) - packet: %+v, relayer: %v", modulePacket, relayer)) - - ackResponse, err := UnpackAcknowledgementResponse(ctx, im.keeper.Logger(ctx), acknowledgement, true) - if err != nil { - errMsg := fmt.Sprintf("Unable to unpack message data from acknowledgement, Sequence %d, from %s %s, to %s %s: %s", - modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort, err.Error()) - im.keeper.Logger(ctx).Error(errMsg) - return errorsmod.Wrapf(types.ErrInvalidAcknowledgement, errMsg) - } - - ackInfo := fmt.Sprintf("sequence #%d, from %s %s, to %s %s", - modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort) - im.keeper.Logger(ctx).Info(fmt.Sprintf("Acknowledgement was successfully unmarshalled: ackInfo: %s", ackInfo)) - - eventType := "ack" - ctx.EventManager().EmitEvent( - sdk.NewEvent( - eventType, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(types.AttributeKeyAck, ackInfo), - ), - ) - - if err := im.keeper.CallRegisteredICACallback(ctx, modulePacket, ackResponse); err != nil { - errMsg := fmt.Sprintf("Unable to call registered ICACallback from OnAcknowledgePacket | Sequence %d, from %s %s, to %s %s", - modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort) - im.keeper.Logger(ctx).Error(errMsg) - return errorsmod.Wrapf(types.ErrCallbackFailed, errMsg) - } - return nil -} - -// OnTimeoutPacket routes the timeout to the relevant callback function -func (im IBCModule) OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) error { - im.keeper.Logger(ctx).Info(fmt.Sprintf("OnTimeoutPacket (ICACallbacks): packet %v, relayer %v", packet, relayer)) - - ackResponse := types.AcknowledgementResponse{ - Status: types.AckResponseStatus_TIMEOUT, - } - - if err := im.keeper.CallRegisteredICACallback(ctx, packet, &ackResponse); err != nil { - errMsg := fmt.Sprintf("Unable to call registered ICACallback from OnTimeoutPacket, Packet: %+v", packet) - im.keeper.Logger(ctx).Error(errMsg) - return errorsmod.Wrapf(types.ErrCallbackFailed, errMsg) - } - return nil -} - -// OnRecvPacket should not be executed in the ICA stack -func (im IBCModule) OnRecvPacket( - ctx sdk.Context, - modulePacket channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - panic("UNIMPLEMENTED") -} - -// No custom logic required in NegotiateAppVersion -func (im IBCModule) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (version string, err error) { - return proposedVersion, nil -} diff --git a/x/icacallbacks/keeper/keeper.go b/x/icacallbacks/keeper/keeper.go index f98ca1e58e..264f939606 100644 --- a/x/icacallbacks/keeper/keeper.go +++ b/x/icacallbacks/keeper/keeper.go @@ -26,7 +26,7 @@ type ( storeKey storetypes.StoreKey memKey storetypes.StoreKey paramstore paramtypes.Subspace - icacallbacks map[string]types.ICACallback + icacallbacks map[string]types.ICACallbackHandler IBCKeeper ibckeeper.Keeper } ) @@ -48,7 +48,7 @@ func NewKeeper( storeKey: storeKey, memKey: memKey, paramstore: ps, - icacallbacks: make(map[string]types.ICACallback), + icacallbacks: make(map[string]types.ICACallbackHandler), IBCKeeper: ibcKeeper, } } @@ -57,38 +57,77 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -func (k Keeper) SetICACallbacks(moduleCallbacks ...types.ModuleCallbacks) error { - for _, callbacks := range moduleCallbacks { - for _, callback := range callbacks { - if _, found := k.icacallbacks[callback.CallbackId]; found { - return fmt.Errorf("callback for ID %s already registered", callback.CallbackId) - } - k.icacallbacks[callback.CallbackId] = callback - } +// Should we add a `AddICACallback` +func (k *Keeper) SetICACallbackHandler(module string, handler types.ICACallbackHandler) error { + _, found := k.icacallbacks[module] + if found { + return fmt.Errorf("callback handler already set for %s", module) } + k.icacallbacks[module] = handler.RegisterICACallbacks() return nil } -func (k Keeper) CallRegisteredICACallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *types.AcknowledgementResponse) error { - // Get the callback key and associated callback data from the packet - callbackDataKey := types.PacketID(packet.GetSourcePort(), packet.GetSourceChannel(), packet.Sequence) +func (k *Keeper) GetICACallbackHandler(module string) (types.ICACallbackHandler, error) { + callback, found := k.icacallbacks[module] + if !found { + return nil, fmt.Errorf("no callback handler found for %s", module) + } + return callback, nil +} + +func (k Keeper) GetCallbackDataFromPacket(ctx sdk.Context, modulePacket channeltypes.Packet, callbackDataKey string) (cbd *types.CallbackData, found bool) { + // get the relevant module from the channel and port + portID := modulePacket.GetSourcePort() + channelID := modulePacket.GetSourceChannel() + // fetch the callback data callbackData, found := k.GetCallbackData(ctx, callbackDataKey) if !found { - k.Logger(ctx).Info(fmt.Sprintf("callback data not found for portID: %s, channelID: %s, sequence: %d", - packet.SourcePort, packet.SourceChannel, packet.Sequence)) - return nil + k.Logger(ctx).Info(fmt.Sprintf("callback data not found for portID: %s, channelID: %s, sequence: %d", portID, channelID, modulePacket.Sequence)) + return nil, false + } else { + k.Logger(ctx).Info(fmt.Sprintf("callback data found for portID: %s, channelID: %s, sequence: %d", portID, channelID, modulePacket.Sequence)) + } + return &callbackData, true +} + +func (k Keeper) GetICACallbackHandlerFromPacket(ctx sdk.Context, modulePacket channeltypes.Packet) (*types.ICACallbackHandler, error) { + module, _, err := k.IBCKeeper.ChannelKeeper.LookupModuleByChannel(ctx, modulePacket.GetSourcePort(), modulePacket.GetSourceChannel()) + if err != nil { + k.Logger(ctx).Error(fmt.Sprintf("error LookupModuleByChannel for portID: %s, channelID: %s, sequence: %d", modulePacket.GetSourcePort(), modulePacket.GetSourceChannel(), modulePacket.Sequence)) + return nil, err } + // fetch the callback function + callbackHandler, err := k.GetICACallbackHandler(module) + if err != nil { + return nil, errorsmod.Wrapf(types.ErrCallbackHandlerNotFound, "Callback handler does not exist for module %s | err: %s", module, err.Error()) + } + return &callbackHandler, nil +} - // If there's an associated callback function, execute it - callback, found := k.icacallbacks[callbackData.CallbackId] +func (k Keeper) CallRegisteredICACallback(ctx sdk.Context, modulePacket channeltypes.Packet, ackResponse *types.AcknowledgementResponse) error { + callbackDataKey := types.PacketID(modulePacket.GetSourcePort(), modulePacket.GetSourceChannel(), modulePacket.Sequence) + callbackData, found := k.GetCallbackDataFromPacket(ctx, modulePacket, callbackDataKey) if !found { - k.Logger(ctx).Info(fmt.Sprintf("No associated callback with callback data %v", callbackData)) return nil } - if err := callback.CallbackFunc(ctx, packet, ackResponse, callbackData.CallbackArgs); err != nil { - errMsg := fmt.Sprintf("Error occured while calling ICACallback (%s) | err: %s", callbackData.CallbackId, err.Error()) - k.Logger(ctx).Error(errMsg) - return errorsmod.Wrapf(types.ErrCallbackFailed, errMsg) + callbackHandler, err := k.GetICACallbackHandlerFromPacket(ctx, modulePacket) + if err != nil { + k.Logger(ctx).Error(fmt.Sprintf("GetICACallbackHandlerFromPacket %s", err.Error())) + return err + } + + // call the callback + if (*callbackHandler).HasICACallback(callbackData.CallbackId) { + k.Logger(ctx).Info(fmt.Sprintf("Calling callback for %s", callbackData.CallbackId)) + // if acknowledgement is empty, then it is a timeout + err := (*callbackHandler).CallICACallback(ctx, callbackData.CallbackId, modulePacket, ackResponse, callbackData.CallbackArgs) + if err != nil { + errMsg := fmt.Sprintf("Error occured while calling ICACallback (%s) | err: %s", callbackData.CallbackId, err.Error()) + k.Logger(ctx).Error(errMsg) + return errorsmod.Wrapf(types.ErrCallbackFailed, errMsg) + } + } else { + k.Logger(ctx).Error(fmt.Sprintf("Callback %v has no associated callback", callbackData)) } // remove the callback data diff --git a/x/icacallbacks/types/callbacks.go b/x/icacallbacks/types/callbacks.go index e2c3a5239f..04f4c39855 100644 --- a/x/icacallbacks/types/callbacks.go +++ b/x/icacallbacks/types/callbacks.go @@ -6,11 +6,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -type ICACallbackFunction func(sdk.Context, channeltypes.Packet, *AcknowledgementResponse, []byte) error - -type ICACallback struct { - CallbackId string - CallbackFunc ICACallbackFunction +type ICACallbackHandler interface { + AddICACallback(id string, fn interface{}) ICACallbackHandler + RegisterICACallbacks() ICACallbackHandler + CallICACallback(ctx sdk.Context, id string, packet channeltypes.Packet, ackResponse *AcknowledgementResponse, args []byte) error + HasICACallback(id string) bool } - -type ModuleCallbacks []ICACallback diff --git a/x/records/keeper/callback_transfer.go b/x/records/keeper/callback_transfer.go index 7ad0a33c6c..307e00e67c 100644 --- a/x/records/keeper/callback_transfer.go +++ b/x/records/keeper/callback_transfer.go @@ -33,7 +33,7 @@ func (k Keeper) UnmarshalTransferCallbackArgs(ctx sdk.Context, delegateCallback return &unmarshalledTransferCallback, nil } -func (k Keeper) TransferCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +func TransferCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { k.Logger(ctx).Info("TransferCallback executing", "packet", packet) // deserialize the args diff --git a/x/records/keeper/callback_transfer_test.go b/x/records/keeper/callback_transfer_test.go index 9e6ee14bd0..a9889ef3ce 100644 --- a/x/records/keeper/callback_transfer_test.go +++ b/x/records/keeper/callback_transfer_test.go @@ -9,6 +9,7 @@ import ( sdkmath "cosmossdk.io/math" icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" + recordskeeper "github.com/Stride-Labs/stride/v12/x/records/keeper" "github.com/Stride-Labs/stride/v12/x/records/types" recordtypes "github.com/Stride-Labs/stride/v12/x/records/types" ) @@ -65,7 +66,7 @@ func (s *KeeperTestSuite) TestTransferCallback_Successful() { initialState := tc.initialState validArgs := tc.validArgs - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err) // Confirm deposit record has been updated to DELEGATION_QUEUE @@ -87,7 +88,7 @@ func (s *KeeperTestSuite) TestTransferCallback_TransferCallbackTimeout() { timeoutArgs := tc.validArgs timeoutArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, timeoutArgs.packet, timeoutArgs.ackResponse, timeoutArgs.args) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, timeoutArgs.packet, timeoutArgs.ackResponse, timeoutArgs.args) s.Require().NoError(err) s.checkTransferStateIfCallbackFailed(tc) } @@ -99,7 +100,7 @@ func (s *KeeperTestSuite) TestTransferCallback_TransferCallbackErrorOnHost() { errorArgs := tc.validArgs errorArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, errorArgs.packet, errorArgs.ackResponse, errorArgs.args) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, errorArgs.packet, errorArgs.ackResponse, errorArgs.args) s.Require().NoError(err) // Confirm deposit record status is reverted @@ -116,7 +117,7 @@ func (s *KeeperTestSuite) TestTransferCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "cannot unmarshal transfer callback args: unexpected EOF: cannot unmarshal") s.checkTransferStateIfCallbackFailed(tc) } @@ -127,7 +128,7 @@ func (s *KeeperTestSuite) TestTransferCallback_DepositRecordNotFound() { // Remove deposit record from store s.App.RecordsKeeper.RemoveDepositRecord(s.Ctx, tc.initialState.callbackArgs.DepositRecordId) - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, fmt.Sprintf("deposit record not found %d: unknown deposit record", tc.initialState.callbackArgs.DepositRecordId)) } @@ -138,6 +139,6 @@ func (s *KeeperTestSuite) TestTransferCallback_PacketUnmarshallingError() { invalidArgs := tc.validArgs invalidArgs.packet.Data = []byte("random bytes") - err := s.App.RecordsKeeper.TransferCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := recordskeeper.TransferCallback(s.App.RecordsKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().EqualError(err, "cannot unmarshal ICS-20 transfer packet data: invalid character 'r' looking for beginning of value: unknown request") } diff --git a/x/records/keeper/callbacks.go b/x/records/keeper/callbacks.go index 7437f3b740..f9701aaab4 100644 --- a/x/records/keeper/callbacks.go +++ b/x/records/keeper/callbacks.go @@ -1,13 +1,43 @@ package keeper import ( + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + icacallbackstypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" ) const TRANSFER = "transfer" -func (k Keeper) Callbacks() icacallbackstypes.ModuleCallbacks { - return []icacallbackstypes.ICACallback{ - {CallbackId: TRANSFER, CallbackFunc: icacallbackstypes.ICACallbackFunction(k.TransferCallback)}, - } +// ICACallbacks wrapper struct for stakeibc keeper +type ICACallback func(Keeper, sdk.Context, channeltypes.Packet, *icacallbackstypes.AcknowledgementResponse, []byte) error + +type ICACallbacks struct { + k Keeper + icacallbacks map[string]ICACallback +} + +var _ icacallbackstypes.ICACallbackHandler = ICACallbacks{} + +func (k Keeper) ICACallbackHandler() ICACallbacks { + return ICACallbacks{k, make(map[string]ICACallback)} +} + +func (c ICACallbacks) CallICACallback(ctx sdk.Context, id string, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { + return c.icacallbacks[id](c.k, ctx, packet, ackResponse, args) +} + +func (c ICACallbacks) HasICACallback(id string) bool { + _, found := c.icacallbacks[id] + return found +} + +func (c ICACallbacks) AddICACallback(id string, fn interface{}) icacallbackstypes.ICACallbackHandler { + c.icacallbacks[id] = fn.(ICACallback) + return c +} + +func (c ICACallbacks) RegisterICACallbacks() icacallbackstypes.ICACallbackHandler { + a := c.AddICACallback(TRANSFER, ICACallback(TransferCallback)) + return a.(ICACallbacks) } diff --git a/x/stakeibc/ibc_middleware.go b/x/stakeibc/ibc_middleware.go deleted file mode 100644 index d3be85baea..0000000000 --- a/x/stakeibc/ibc_middleware.go +++ /dev/null @@ -1,220 +0,0 @@ -package stakeibc - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" - 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" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - - "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" - "github.com/Stride-Labs/stride/v12/x/stakeibc/types" -) - -var _ porttypes.Middleware = &IBCMiddleware{} - -type IBCMiddleware struct { - app porttypes.IBCModule - keeper keeper.Keeper -} - -func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { - return IBCMiddleware{ - app: app, - keeper: k, - } -} - -// OnChanOpenInit simply passes down the to next middleware stack -func (im IBCMiddleware) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - channelCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) (string, error) { - return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) -} - -// OnChanOpenTry simply passes down the to next middleware stack -func (im IBCMiddleware) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, - channelID string, - chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - counterpartyVersion string, -) (string, error) { - return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) -} - -// OnChanOpenAck stores the new ICA acccount addresses on the host zone and then passes to the next middleware stack -func (im IBCMiddleware) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - counterpartyChannelID string, - counterpartyVersion string, -) error { - im.keeper.Logger(ctx).Info(fmt.Sprintf("OnChanOpenAck: portID %s, channelID %s, counterpartyChannelID %s, counterpartyVersion %s", portID, channelID, counterpartyChannelID, counterpartyVersion)) - controllerConnectionId, err := im.keeper.GetConnectionId(ctx, portID) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("Unable to get connection for port: %s", portID)) - } - address, found := im.keeper.ICAControllerKeeper.GetInterchainAccountAddress(ctx, controllerConnectionId, portID) - if !found { - ctx.Logger().Error(fmt.Sprintf("Expected to find an address for %s/%s", controllerConnectionId, portID)) - return nil - } - // get host chain id from connection - // fetch counterparty connection - hostChainId, err := im.keeper.GetChainID(ctx, controllerConnectionId) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("Unable to obtain counterparty chain for connection: %s, port: %s, err: %s", controllerConnectionId, portID, err.Error())) - return nil - } - // get zone info - zoneInfo, found := im.keeper.GetHostZone(ctx, hostChainId) - if !found { - ctx.Logger().Error(fmt.Sprintf("Expected to find zone info for %v", hostChainId)) - return nil - } - ctx.Logger().Info(fmt.Sprintf("Found matching address for chain: %s, address %s, port %s", zoneInfo.ChainId, address, portID)) - - // addresses - withdrawalAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_WITHDRAWAL)) - if err != nil { - return err - } - feeAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_FEE)) - if err != nil { - return err - } - delegationAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_DELEGATION)) - if err != nil { - return err - } - redemptionAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_REDEMPTION)) - if err != nil { - return err - } - - // Set ICA account addresses - switch { - // withdrawal address - case portID == withdrawalAddress: - zoneInfo.WithdrawalAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_WITHDRAWAL} - // fee address - case portID == feeAddress: - zoneInfo.FeeAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_FEE} - // delegation address - case portID == delegationAddress: - zoneInfo.DelegationAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_DELEGATION} - case portID == redemptionAddress: - zoneInfo.RedemptionAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_REDEMPTION} - default: - ctx.Logger().Error(fmt.Sprintf("Missing portId: %s", portID)) - } - - im.keeper.SetHostZone(ctx, zoneInfo) - - // call underlying app's OnChanOpenAck - return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) -} - -// OnChanCloseConfirm simply passes down the to next middleware stack -func (im IBCMiddleware) OnChanCloseConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanCloseConfirm(ctx, portID, channelID) -} - -// OnChanCloseInit simply passes down the to next middleware stack -func (im IBCMiddleware) OnChanCloseInit( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanCloseInit(ctx, portID, channelID) -} - -// OnChanOpenConfirm simply passes down the to next middleware stack -func (im IBCMiddleware) OnChanOpenConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanOpenConfirm(ctx, portID, channelID) -} - -// OnAcknowledgementPacket simply passes down the to next middleware stack -// The Ack handling and routing is managed by icacallbacks -func (im IBCMiddleware) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) -} - -// OnTimeoutPacket simply passes down the to next middleware stack -// The Ack handling and routing is managed by icacallbacks -func (im IBCMiddleware) OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) error { - return im.app.OnTimeoutPacket(ctx, packet, relayer) -} - -// OnRecvPacket simply passes down the to next middleware stack -func (im IBCMiddleware) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - return im.app.OnRecvPacket(ctx, packet, relayer) -} - -// SendPacket implements the ICS4 Wrapper interface but is not utilized in the ICA stack -// but is not utilized in the bottom of ICA stack -func (im IBCMiddleware) SendPacket( - ctx sdk.Context, - chanCap *capabilitytypes.Capability, - sourcePort string, - sourceChannel string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - data []byte, -) (sequence uint64, err error) { - panic("UNIMPLEMENTED") -} - -// WriteAcknowledgement implements the ICS4 Wrapper interface -// but is not utilized in the bottom of ICA stack -func (im IBCMiddleware) WriteAcknowledgement( - ctx sdk.Context, - chanCap *capabilitytypes.Capability, - packet ibcexported.PacketI, - ack ibcexported.Acknowledgement, -) error { - panic("UNIMPLEMENTED") -} - -// GetAppVersion implements the ICS4 Wrapper interface -// but is not utilized in the bottom of ICA stack -func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { - panic("UNIMPLEMENTED") -} diff --git a/x/stakeibc/keeper/icacallbacks.go b/x/stakeibc/keeper/icacallbacks.go index 335f2f223f..fe70daea7e 100644 --- a/x/stakeibc/keeper/icacallbacks.go +++ b/x/stakeibc/keeper/icacallbacks.go @@ -1,7 +1,9 @@ package keeper import ( - "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + icacallbackstypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" ) @@ -14,13 +16,41 @@ const ( ICACallbackID_Rebalance = "rebalance" ) -func (k Keeper) Callbacks() icacallbackstypes.ModuleCallbacks { - return []types.ICACallback{ - {CallbackId: ICACallbackID_Delegate, CallbackFunc: types.ICACallbackFunction(k.DelegateCallback)}, - {CallbackId: ICACallbackID_Claim, CallbackFunc: types.ICACallbackFunction(k.ClaimCallback)}, - {CallbackId: ICACallbackID_Undelegate, CallbackFunc: types.ICACallbackFunction(k.UndelegateCallback)}, - {CallbackId: ICACallbackID_Reinvest, CallbackFunc: types.ICACallbackFunction(k.ReinvestCallback)}, - {CallbackId: ICACallbackID_Redemption, CallbackFunc: types.ICACallbackFunction(k.RedemptionCallback)}, - {CallbackId: ICACallbackID_Rebalance, CallbackFunc: types.ICACallbackFunction(k.RebalanceCallback)}, - } +// ICACallbacks wrapper struct for stakeibc keeper +type ICACallback func(Keeper, sdk.Context, channeltypes.Packet, *icacallbackstypes.AcknowledgementResponse, []byte) error + +type ICACallbacks struct { + k Keeper + icacallbacks map[string]ICACallback +} + +var _ icacallbackstypes.ICACallbackHandler = ICACallbacks{} + +func (k Keeper) ICACallbackHandler() ICACallbacks { + return ICACallbacks{k, make(map[string]ICACallback)} +} + +func (c ICACallbacks) CallICACallback(ctx sdk.Context, id string, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { + return c.icacallbacks[id](c.k, ctx, packet, ackResponse, args) +} + +func (c ICACallbacks) HasICACallback(id string) bool { + _, found := c.icacallbacks[id] + return found +} + +func (c ICACallbacks) AddICACallback(id string, fn interface{}) icacallbackstypes.ICACallbackHandler { + c.icacallbacks[id] = fn.(ICACallback) + return c +} + +func (c ICACallbacks) RegisterICACallbacks() icacallbackstypes.ICACallbackHandler { + a := c. + AddICACallback(ICACallbackID_Delegate, ICACallback(DelegateCallback)). + AddICACallback(ICACallbackID_Claim, ICACallback(ClaimCallback)). + AddICACallback(ICACallbackID_Undelegate, ICACallback(UndelegateCallback)). + AddICACallback(ICACallbackID_Reinvest, ICACallback(ReinvestCallback)). + AddICACallback(ICACallbackID_Redemption, ICACallback(RedemptionCallback)). + AddICACallback(ICACallbackID_Rebalance, ICACallback(RebalanceCallback)) + return a.(ICACallbacks) } diff --git a/x/stakeibc/keeper/icacallbacks_claim.go b/x/stakeibc/keeper/icacallbacks_claim.go index 80f541ea22..f0503a5231 100644 --- a/x/stakeibc/keeper/icacallbacks_claim.go +++ b/x/stakeibc/keeper/icacallbacks_claim.go @@ -35,9 +35,12 @@ func (k Keeper) UnmarshalClaimCallbackArgs(ctx sdk.Context, claimCallback []byte } // ICA Callback after claiming unbonded tokens -// * If successful: Removes the user redemption record -// * If timeout/failure: Reverts pending flag in the user redemption record so the claim can be re-tried -func (k Keeper) ClaimCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +// +// If successful: +// * Removes the user redemption record +// If timeout/failure: +// * Reverts pending flag in the user redemption record so the claim can be re-tried +func ClaimCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Fetch callback args claimCallback, err := k.UnmarshalClaimCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_claim_test.go b/x/stakeibc/keeper/icacallbacks_claim_test.go index 44a4fb2269..e919ee6129 100644 --- a/x/stakeibc/keeper/icacallbacks_claim_test.go +++ b/x/stakeibc/keeper/icacallbacks_claim_test.go @@ -9,6 +9,7 @@ import ( icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" recordtypes "github.com/Stride-Labs/stride/v12/x/records/types" + stakeibckeeper "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v12/x/stakeibc/types" ) @@ -121,7 +122,7 @@ func (s *KeeperTestSuite) TestClaimCallback_Successful() { initialState := tc.initialState validArgs := tc.validArgs - err := s.App.StakeibcKeeper.ClaimCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := stakeibckeeper.ClaimCallback(s.App.StakeibcKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err) _, found := s.App.RecordsKeeper.GetUserRedemptionRecord(s.Ctx, initialState.callbackArgs.UserRedemptionRecordId) @@ -164,7 +165,7 @@ func (s *KeeperTestSuite) TestClaimCallback_ClaimCallbackTimeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.ClaimCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ClaimCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err, "timeout successfully proccessed") s.checkClaimStateIfCallbackFailed(tc) } @@ -176,7 +177,7 @@ func (s *KeeperTestSuite) TestClaimCallback_ClaimCallbackErrorOnHost() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.ClaimCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ClaimCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err, "error ack successfully proccessed") s.checkClaimStateIfCallbackFailed(tc) } @@ -187,7 +188,7 @@ func (s *KeeperTestSuite) TestClaimCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.ClaimCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.ClaimCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal claim callback args: unexpected EOF: unable to unmarshal data structure") } @@ -197,7 +198,7 @@ func (s *KeeperTestSuite) TestClaimCallback_RecordNotFound() { // Remove the user redemption record from the state s.App.RecordsKeeper.RemoveUserRedemptionRecord(s.Ctx, tc.initialState.callbackArgs.UserRedemptionRecordId) - err := s.App.StakeibcKeeper.ClaimCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.ClaimCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, fmt.Sprintf("user redemption record not found %s: record not found", tc.initialState.callbackArgs.UserRedemptionRecordId)) } diff --git a/x/stakeibc/keeper/icacallbacks_delegate.go b/x/stakeibc/keeper/icacallbacks_delegate.go index 6945ddd8d1..403077d00c 100644 --- a/x/stakeibc/keeper/icacallbacks_delegate.go +++ b/x/stakeibc/keeper/icacallbacks_delegate.go @@ -39,10 +39,14 @@ func (k Keeper) UnmarshalDelegateCallbackArgs(ctx sdk.Context, delegateCallback } // ICA Callback after delegating deposit records -// * If successful: Updates deposit record status and records delegation changes on the host zone and validators -// * If timeout: Does nothing -// * If failure: Reverts deposit record status -func (k Keeper) DelegateCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +// +// If successful: +// * Updates deposit record status and records delegation changes on the host zone and validators +// If timeout: +// * Does nothing +// If failure: +// * Reverts deposit record status +func DelegateCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Deserialize the callback args delegateCallback, err := k.UnmarshalDelegateCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_delegate_test.go b/x/stakeibc/keeper/icacallbacks_delegate_test.go index a45bc87c53..f1b8a8e193 100644 --- a/x/stakeibc/keeper/icacallbacks_delegate_test.go +++ b/x/stakeibc/keeper/icacallbacks_delegate_test.go @@ -9,7 +9,9 @@ import ( icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" recordtypes "github.com/Stride-Labs/stride/v12/x/records/types" + stakeibckeeper "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v12/x/stakeibc/types" + stakeibc "github.com/Stride-Labs/stride/v12/x/stakeibc/types" ) type DelegateCallbackState struct { @@ -52,7 +54,7 @@ func (s *KeeperTestSuite) SetupDelegateCallback() DelegateCallbackTestCase { Address: "val2_address", DelegationAmt: val2Bal, } - hostZone := types.HostZone{ + hostZone := stakeibc.HostZone{ ChainId: HostChainId, HostDenom: Atom, IbcDenom: IbcAtom, @@ -115,7 +117,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_Successful() { initialState := tc.initialState validArgs := tc.validArgs - err := s.App.StakeibcKeeper.DelegateCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err) // Confirm stakedBal has increased @@ -154,7 +156,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_DelegateCallbackTimeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.DelegateCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkDelegateStateIfCallbackFailed(tc) } @@ -166,7 +168,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_DelegateCallbackErrorOnHost() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.DelegateCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkDelegateStateIfCallbackFailed(tc) } @@ -177,7 +179,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.DelegateCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal delegate callback args: unexpected EOF: unable to unmarshal data structure") s.checkDelegateStateIfCallbackFailed(tc) } @@ -188,7 +190,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_HostNotFound() { // Remove the host zone s.App.StakeibcKeeper.RemoveHostZone(s.Ctx, HostChainId) - err := s.App.StakeibcKeeper.DelegateCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, "host zone not found GAIA: invalid request") // Confirm deposit record has NOT been removed @@ -214,7 +216,7 @@ func (s *KeeperTestSuite) TestDelegateCallback_MissingValidator() { invalidCallbackArgs, err := s.App.StakeibcKeeper.MarshalDelegateCallbackArgs(s.Ctx, callbackArgs) s.Require().NoError(err) - err = s.App.StakeibcKeeper.DelegateCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) + err = stakeibckeeper.DelegateCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Failed to add delegation to validator: can't change delegation on validator") s.checkDelegateStateIfCallbackFailed(tc) } diff --git a/x/stakeibc/keeper/icacallbacks_rebalance.go b/x/stakeibc/keeper/icacallbacks_rebalance.go index 8872e17063..e308dfb47a 100644 --- a/x/stakeibc/keeper/icacallbacks_rebalance.go +++ b/x/stakeibc/keeper/icacallbacks_rebalance.go @@ -35,9 +35,12 @@ func (k Keeper) UnmarshalRebalanceCallbackArgs(ctx sdk.Context, rebalanceCallbac } // ICA Callback after rebalance validators on a host zone -// * If successful: Updates relevant validator delegations on the host zone struct -// * If timeout/failure: Does nothing -func (k Keeper) RebalanceCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +// +// If successful: +// * Updates relevant validator delegations on the host zone struct +// If timeout/failure: +// * Does nothing +func RebalanceCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Fetch callback args rebalanceCallback, err := k.UnmarshalRebalanceCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_rebalance_test.go b/x/stakeibc/keeper/icacallbacks_rebalance_test.go index fa5eb5f034..171c473bf7 100644 --- a/x/stakeibc/keeper/icacallbacks_rebalance_test.go +++ b/x/stakeibc/keeper/icacallbacks_rebalance_test.go @@ -6,12 +6,14 @@ import ( _ "github.com/stretchr/testify/suite" icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" + stakeibckeeper "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v12/x/stakeibc/types" + stakeibctypes "github.com/Stride-Labs/stride/v12/x/stakeibc/types" ) type RebalanceCallbackState struct { - hostZone types.HostZone - initialValidators []*types.Validator + hostZone stakeibctypes.HostZone + initialValidators []*stakeibctypes.Validator } type RebalanceCallbackArgs struct { @@ -64,7 +66,7 @@ func (s *KeeperTestSuite) SetupRebalanceCallback() RebalanceCallbackTestCase { func (s *KeeperTestSuite) TestRebalanceCallback_Successful() { tc := s.SetupRebalanceCallback() - err := s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().NoError(err, "rebalance callback succeeded") hz, found := s.App.StakeibcKeeper.GetHostZone(s.Ctx, "GAIA") @@ -101,7 +103,7 @@ func (s *KeeperTestSuite) TestRebalanceCallback_Timeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkDelegationStateIfCallbackFailed() } @@ -113,7 +115,7 @@ func (s *KeeperTestSuite) TestRebalanceCallback_ErrorOnHost() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkDelegationStateIfCallbackFailed() } @@ -125,7 +127,7 @@ func (s *KeeperTestSuite) TestRebalanceCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal rebalance callback args: unexpected EOF: unable to unmarshal data structure") s.checkDelegationStateIfCallbackFailed() } @@ -169,11 +171,11 @@ func (s *KeeperTestSuite) TestRebalanceCallback_WrongValidator() { invalidArgsTwo, err := s.App.StakeibcKeeper.MarshalRebalanceCallbackArgs(s.Ctx, callbackArgs) s.Require().NoError(err) - err = s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidArgsOne) + err = stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidArgsOne) s.Require().EqualError(err, "validator not found stride_VAL4_WRONG: invalid request") s.checkDelegationStateIfCallbackFailed() - err = s.App.StakeibcKeeper.RebalanceCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidArgsTwo) + err = stakeibckeeper.RebalanceCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidArgsTwo) s.Require().EqualError(err, "validator not found stride_VAL1_WRONG: invalid request") s.checkDelegationStateIfCallbackFailed() } diff --git a/x/stakeibc/keeper/icacallbacks_redemption.go b/x/stakeibc/keeper/icacallbacks_redemption.go index bcd70f6b54..bfb387cd82 100644 --- a/x/stakeibc/keeper/icacallbacks_redemption.go +++ b/x/stakeibc/keeper/icacallbacks_redemption.go @@ -36,10 +36,14 @@ func (k Keeper) UnmarshalRedemptionCallbackArgs(ctx sdk.Context, redemptionCallb } // ICA Callback after undelegating -// * If successful: Updates epoch unbonding record status -// * If timeout: Does nothing -// * If failure: Reverts epoch unbonding record status -func (k Keeper) RedemptionCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +// +// If successful: +// * Updates epoch unbonding record status +// If timeout: +// * Does nothing +// If failure: +// * Reverts epoch unbonding record status +func RedemptionCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Fetch callback args redemptionCallback, err := k.UnmarshalRedemptionCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_redemption_test.go b/x/stakeibc/keeper/icacallbacks_redemption_test.go index 16387d731f..b970542623 100644 --- a/x/stakeibc/keeper/icacallbacks_redemption_test.go +++ b/x/stakeibc/keeper/icacallbacks_redemption_test.go @@ -10,7 +10,9 @@ import ( icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" recordtypes "github.com/Stride-Labs/stride/v12/x/records/types" + stakeibckeeper "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v12/x/stakeibc/types" + stakeibc "github.com/Stride-Labs/stride/v12/x/stakeibc/types" ) type RedemptionCallbackState struct { @@ -55,7 +57,7 @@ func (s *KeeperTestSuite) SetupRedemptionCallback() RedemptionCallbackTestCase { EpochNumber: epochNumber, HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{&hostZoneUnbonding}, } - hostZone := types.HostZone{ + hostZone := stakeibc.HostZone{ ChainId: HostChainId, HostDenom: Atom, IbcDenom: IbcAtom, @@ -94,7 +96,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_Successful() { initialState := tc.initialState validArgs := tc.validArgs - err := s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err, "redemption callback succeeded") for _, epochNumber := range initialState.epochUnbondingNumbers { @@ -130,7 +132,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_RedemptionCallbackTimeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkRedemptionStateIfCallbackFailed(tc) } @@ -142,7 +144,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_RedemptionCallbackErrorOnHost() invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkRedemptionStateIfCallbackFailed(tc) } @@ -154,7 +156,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal redemption callback args: unexpected EOF: unable to unmarshal data structure") s.checkRedemptionStateIfCallbackFailed(tc) } @@ -171,7 +173,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_EpochUnbondingRecordNotFound() invalidCallbackArgs, err := s.App.StakeibcKeeper.MarshalRedemptionCallbackArgs(s.Ctx, callbackArgs) s.Require().NoError(err) - err = s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) + err = stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) expectedErr := fmt.Sprintf("Error fetching host zone unbonding record for epoch: %d, host zone: GAIA: host zone not found", tc.initialState.epochNumber+1) s.Require().EqualError(err, expectedErr) s.checkRedemptionStateIfCallbackFailed(tc) @@ -186,7 +188,7 @@ func (s *KeeperTestSuite) TestRedemptionCallback_HostZoneUnbondingNotFound() { epochUnbondingRecord.HostZoneUnbondings = []*recordtypes.HostZoneUnbonding{} s.App.RecordsKeeper.SetEpochUnbondingRecord(s.Ctx, epochUnbondingRecord) - err := s.App.StakeibcKeeper.RedemptionCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.RedemptionCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, fmt.Sprintf("Error fetching host zone unbonding record for epoch: %d, host zone: GAIA: host zone not found", tc.initialState.epochNumber)) s.checkRedemptionStateIfCallbackFailed(tc) } diff --git a/x/stakeibc/keeper/icacallbacks_reinvest.go b/x/stakeibc/keeper/icacallbacks_reinvest.go index f8b09e86aa..c70c81bcf9 100644 --- a/x/stakeibc/keeper/icacallbacks_reinvest.go +++ b/x/stakeibc/keeper/icacallbacks_reinvest.go @@ -42,9 +42,13 @@ func (k Keeper) UnmarshalReinvestCallbackArgs(ctx sdk.Context, reinvestCallback } // ICA Callback after reinvestment -// * If successful: Creates a new DepositRecord with the reinvestment amount and issues an ICQ to query the rewards balance -// * If timeout/failure: Does nothing -func (k Keeper) ReinvestCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +// +// If successful: +// * Creates a new DepositRecord with the reinvestment amount +// * Issues an ICQ to query the rewards balance +// If timeout/failure: +// * Does nothing +func ReinvestCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Fetch callback args reinvestCallback, err := k.UnmarshalReinvestCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_reinvest_test.go b/x/stakeibc/keeper/icacallbacks_reinvest_test.go index 0c46098118..1fede7f624 100644 --- a/x/stakeibc/keeper/icacallbacks_reinvest_test.go +++ b/x/stakeibc/keeper/icacallbacks_reinvest_test.go @@ -108,7 +108,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_Successful() { expectedRecord := initialState.depositRecord validArgs := tc.validArgs - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err) // Confirm deposit record has been added @@ -149,7 +149,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_ReinvestCallbackTimeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkReinvestStateIfCallbackFailed(tc) } @@ -161,7 +161,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_ReinvestCallbackErrorOnHost() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err) s.checkReinvestStateIfCallbackFailed(tc) } @@ -173,7 +173,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal reinvest callback args: unexpected EOF: unable to unmarshal data structure") } @@ -183,7 +183,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_HostZoneNotFound() { // Remove the host zone s.App.StakeibcKeeper.RemoveHostZone(s.Ctx, HostChainId) - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().ErrorContains(err, "host zone GAIA not found: host zone not found") } @@ -195,7 +195,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_NoFeeAccount() { badHostZone.FeeAccount = nil s.App.StakeibcKeeper.SetHostZone(s.Ctx, badHostZone) - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, "no fee account found for GAIA: ICA acccount not found on host zone") } @@ -207,7 +207,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_InvalidFeeAccountAddress() { badHostZone.FeeAccount.Address = "invalid_fee_account" s.App.StakeibcKeeper.SetHostZone(s.Ctx, badHostZone) - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().ErrorContains(err, "invalid fee account address, could not decode") } @@ -218,7 +218,7 @@ func (s *KeeperTestSuite) TestReinvestCallback_MissingEpoch() { // Remove epoch tracker s.App.StakeibcKeeper.RemoveEpochTracker(s.Ctx, epochtypes.STRIDE_EPOCH) - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().ErrorContains(err, "no number for epoch (stride_epoch)") } @@ -231,6 +231,6 @@ func (s *KeeperTestSuite) TestReinvestCallback_FailedToSubmitQuery() { badHostZone.ConnectionId = "" s.App.StakeibcKeeper.SetHostZone(s.Ctx, badHostZone) - err := s.App.StakeibcKeeper.ReinvestCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.ReinvestCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().EqualError(err, "[ICQ Validation Check] Failed! connection id cannot be empty: invalid request") } diff --git a/x/stakeibc/keeper/icacallbacks_undelegate.go b/x/stakeibc/keeper/icacallbacks_undelegate.go index cd43b884b1..750aed586d 100644 --- a/x/stakeibc/keeper/icacallbacks_undelegate.go +++ b/x/stakeibc/keeper/icacallbacks_undelegate.go @@ -50,7 +50,7 @@ func (k Keeper) UnmarshalUndelegateCallbackArgs(ctx sdk.Context, undelegateCallb // * Does nothing // If failure: // * Reverts epoch unbonding record status -func (k Keeper) UndelegateCallback(ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { +func UndelegateCallback(k Keeper, ctx sdk.Context, packet channeltypes.Packet, ackResponse *icacallbackstypes.AcknowledgementResponse, args []byte) error { // Fetch callback args undelegateCallback, err := k.UnmarshalUndelegateCallbackArgs(ctx, args) if err != nil { diff --git a/x/stakeibc/keeper/icacallbacks_undelegate_test.go b/x/stakeibc/keeper/icacallbacks_undelegate_test.go index 697a660fcc..30d1779a7d 100644 --- a/x/stakeibc/keeper/icacallbacks_undelegate_test.go +++ b/x/stakeibc/keeper/icacallbacks_undelegate_test.go @@ -13,7 +13,9 @@ import ( icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" recordtypes "github.com/Stride-Labs/stride/v12/x/records/types" + stakeibckeeper "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" "github.com/Stride-Labs/stride/v12/x/stakeibc/types" + stakeibc "github.com/Stride-Labs/stride/v12/x/stakeibc/types" ) type UndelegateCallbackState struct { @@ -65,7 +67,7 @@ func (s *KeeperTestSuite) SetupUndelegateCallback() UndelegateCallbackTestCase { acc: zoneAddress, stAtomBalance: sdk.NewCoin(StAtom, zoneAccountBalance), // Add a few extra tokens to make the test more robust } - hostZone := types.HostZone{ + hostZone := stakeibc.HostZone{ ChainId: HostChainId, HostDenom: Atom, IbcDenom: IbcAtom, @@ -147,7 +149,7 @@ func (s *KeeperTestSuite) TestUndelegateCallback_Successful() { validArgs := tc.validArgs // Callback - err := s.App.StakeibcKeeper.UndelegateCallback(s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) + err := stakeibckeeper.UndelegateCallback(s.App.StakeibcKeeper, s.Ctx, validArgs.packet, validArgs.ackResponse, validArgs.args) s.Require().NoError(err, "undelegate callback succeeds") // Check that stakedBal has decreased on the host zone @@ -208,7 +210,7 @@ func (s *KeeperTestSuite) TestUndelegateCallback_UndelegateCallbackTimeout() { invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_TIMEOUT - err := s.App.StakeibcKeeper.UndelegateCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.UndelegateCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err, "undelegate callback succeeds on timeout") s.checkStateIfUndelegateCallbackFailed(tc) } @@ -220,7 +222,7 @@ func (s *KeeperTestSuite) TestUndelegateCallback_UndelegateCallbackErrorOnHost() invalidArgs := tc.validArgs invalidArgs.ackResponse.Status = icacallbacktypes.AckResponseStatus_FAILURE - err := s.App.StakeibcKeeper.UndelegateCallback(s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) + err := stakeibckeeper.UndelegateCallback(s.App.StakeibcKeeper, s.Ctx, invalidArgs.packet, invalidArgs.ackResponse, invalidArgs.args) s.Require().NoError(err, "undelegate callback succeeds with error on host") s.checkStateIfUndelegateCallbackFailed(tc) } @@ -231,7 +233,7 @@ func (s *KeeperTestSuite) TestUndelegateCallback_WrongCallbackArgs() { // random args should cause the callback to fail invalidCallbackArgs := []byte("random bytes") - err := s.App.StakeibcKeeper.UndelegateCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) + err := stakeibckeeper.UndelegateCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, invalidCallbackArgs) s.Require().EqualError(err, "Unable to unmarshal undelegate callback args: unexpected EOF: unable to unmarshal data structure") s.checkStateIfUndelegateCallbackFailed(tc) } @@ -242,7 +244,7 @@ func (s *KeeperTestSuite) TestUndelegateCallback_HostNotFound() { // remove the host zone from the store to trigger a host not found error s.App.StakeibcKeeper.RemoveHostZone(s.Ctx, HostChainId) - err := s.App.StakeibcKeeper.UndelegateCallback(s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) + err := stakeibckeeper.UndelegateCallback(s.App.StakeibcKeeper, s.Ctx, tc.validArgs.packet, tc.validArgs.ackResponse, tc.validArgs.args) s.Require().EqualError(err, "Host zone not found: GAIA: key not found") } diff --git a/x/stakeibc/module_ibc.go b/x/stakeibc/module_ibc.go new file mode 100644 index 0000000000..f0fb16e334 --- /dev/null +++ b/x/stakeibc/module_ibc.go @@ -0,0 +1,260 @@ +package stakeibc + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + + "github.com/Stride-Labs/stride/v12/x/icacallbacks" + icacallbacktypes "github.com/Stride-Labs/stride/v12/x/icacallbacks/types" + + ratelimittypes "github.com/Stride-Labs/stride/v12/x/ratelimit/types" + "github.com/Stride-Labs/stride/v12/x/stakeibc/keeper" + "github.com/Stride-Labs/stride/v12/x/stakeibc/types" +) + +// IBCModule implements the ICS26 interface for interchain accounts controller chains +type IBCModule struct { + keeper keeper.Keeper +} + +// NewIBCModule creates a new IBCModule given the keeper +func NewIBCModule(k keeper.Keeper) IBCModule { + return IBCModule{ + keeper: k, + } +} + +func (im IBCModule) Hooks() keeper.Hooks { + return im.keeper.Hooks() +} + +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + im.keeper.Logger(ctx).Info(fmt.Sprintf("OnChanOpenAck: portID %s, channelID %s, counterpartyChannelID %s, counterpartyVersion %s", portID, channelID, counterpartyChannelID, counterpartyVersion)) + controllerConnectionId, err := im.keeper.GetConnectionId(ctx, portID) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Unable to get connection for port: %s", portID)) + } + address, found := im.keeper.ICAControllerKeeper.GetInterchainAccountAddress(ctx, controllerConnectionId, portID) + if !found { + ctx.Logger().Error(fmt.Sprintf("Expected to find an address for %s/%s", controllerConnectionId, portID)) + return nil + } + // get host chain id from connection + // fetch counterparty connection + hostChainId, err := im.keeper.GetChainID(ctx, controllerConnectionId) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Unable to obtain counterparty chain for connection: %s, port: %s, err: %s", controllerConnectionId, portID, err.Error())) + return nil + } + // get zone info + zoneInfo, found := im.keeper.GetHostZone(ctx, hostChainId) + if !found { + ctx.Logger().Error(fmt.Sprintf("Expected to find zone info for %v", hostChainId)) + return nil + } + ctx.Logger().Info(fmt.Sprintf("Found matching address for chain: %s, address %s, port %s", zoneInfo.ChainId, address, portID)) + + // addresses + withdrawalAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_WITHDRAWAL)) + if err != nil { + return err + } + feeAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_FEE)) + if err != nil { + return err + } + delegationAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_DELEGATION)) + if err != nil { + return err + } + redemptionAddress, err := icatypes.NewControllerPortID(types.FormatICAAccountOwner(hostChainId, types.ICAAccountType_REDEMPTION)) + if err != nil { + return err + } + + // Set ICA account addresses + switch { + case portID == withdrawalAddress: + zoneInfo.WithdrawalAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_WITHDRAWAL} + + case portID == feeAddress: + zoneInfo.FeeAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_FEE} + rewardCollectorAddress := im.keeper.AccountKeeper.GetModuleAccount(ctx, types.RewardCollectorName).GetAddress() + im.keeper.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{ + Sender: address, + Receiver: rewardCollectorAddress.String(), + }) + + case portID == delegationAddress: + zoneInfo.DelegationAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_DELEGATION} + im.keeper.RatelimitKeeper.SetWhitelistedAddressPair(ctx, ratelimittypes.WhitelistedAddressPair{ + Sender: zoneInfo.Address, + Receiver: address, + }) + + case portID == redemptionAddress: + zoneInfo.RedemptionAccount = &types.ICAAccount{Address: address, Target: types.ICAAccountType_REDEMPTION} + + default: + ctx.Logger().Error(fmt.Sprintf("Missing portId: %s", portID)) + } + + im.keeper.SetHostZone(ctx, zoneInfo) + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + modulePacket channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + im.keeper.Logger(ctx).Info(fmt.Sprintf("OnAcknowledgementPacket (Stakeibc) - packet: %+v, relayer: %v", modulePacket, relayer)) + + ackResponse, err := icacallbacks.UnpackAcknowledgementResponse(ctx, im.keeper.Logger(ctx), acknowledgement, true) + if err != nil { + errMsg := fmt.Sprintf("Unable to unpack message data from acknowledgement, Sequence %d, from %s %s, to %s %s: %s", + modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort, err.Error()) + im.keeper.Logger(ctx).Error(errMsg) + return errorsmod.Wrapf(icacallbacktypes.ErrInvalidAcknowledgement, errMsg) + } + + ackInfo := fmt.Sprintf("sequence #%d, from %s %s, to %s %s", + modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort) + im.keeper.Logger(ctx).Info(fmt.Sprintf("Acknowledgement was successfully unmarshalled: ackInfo: %s", ackInfo)) + + eventType := "ack" + ctx.EventManager().EmitEvent( + sdk.NewEvent( + eventType, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyAck, ackInfo), + ), + ) + + err = im.keeper.ICACallbacksKeeper.CallRegisteredICACallback(ctx, modulePacket, ackResponse) + if err != nil { + errMsg := fmt.Sprintf("Unable to call registered callback from stakeibc OnAcknowledgePacket | Sequence %d, from %s %s, to %s %s", + modulePacket.Sequence, modulePacket.SourceChannel, modulePacket.SourcePort, modulePacket.DestinationChannel, modulePacket.DestinationPort) + im.keeper.Logger(ctx).Error(errMsg) + return errorsmod.Wrapf(icacallbacktypes.ErrCallbackFailed, errMsg) + } + return nil +} + +// OnTimeoutPacket implements the IBCModule interface +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + modulePacket channeltypes.Packet, + relayer sdk.AccAddress, +) error { + im.keeper.Logger(ctx).Info(fmt.Sprintf("OnTimeoutPacket: packet %v, relayer %v", modulePacket, relayer)) + ackResponse := icacallbacktypes.AcknowledgementResponse{Status: icacallbacktypes.AckResponseStatus_TIMEOUT} + err := im.keeper.ICACallbacksKeeper.CallRegisteredICACallback(ctx, modulePacket, &ackResponse) + if err != nil { + return err + } + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + + // WARNING: For some reason, in IBCv3 the ICA controller module does not call the underlying OnChanCloseConfirm (this function) + // So, we need to put logic that _should_ execute upon channel closure in the OnTimeoutPacket function + // This works because ORDERED channels are always closed when a timeout occurs, but if we migrate to using ORDERED channels that don't + // close on timeout, we will need to move this logic to the OnChanCloseConfirm function + // relevant IBCv3 code: https://github.com/cosmos/ibc-go/blob/5c0bf8b8a0f79643e36be98fb9883ea163d2d93a/modules/apps/27-interchain-accounts/controller/ibc_module.go#L123 + return nil +} + +// ################################################################################### +// Helper functions +// ################################################################################### + +func (im IBCModule) NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, +) (version string, err error) { + return proposedVersion, nil +} + +// ################################################################################### +// Required functions to satisfy interface but not implemented for ICA auth modules +// ################################################################################### + +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + modulePacket channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +}