From 5ba056cb721d23d9cfd2423694dec9d6c3474cca Mon Sep 17 00:00:00 2001 From: zale144 Date: Tue, 25 Jun 2024 19:30:59 +0200 Subject: [PATCH] feat(denommetadata): register IBC denom on transfer (#956) --- CHANGELOG.md | 1 + app/app.go | 7 +- testutil/keeper/delayedack.go | 9 +- x/delayedack/keeper/keeper.go | 34 +- x/delayedack/types/expected_keepers.go | 9 +- x/denommetadata/ibc_middleware.go | 238 ++++++++ x/denommetadata/ibc_middleware_test.go | 655 +++++++++++++++++++++ x/denommetadata/keeper/keeper.go | 13 +- x/denommetadata/keeper/keeper_test.go | 4 +- x/denommetadata/proposal_handler.go | 4 +- x/denommetadata/types/codec.go | 20 +- x/denommetadata/types/errors.go | 12 - x/denommetadata/types/expected_keepers.go | 18 +- x/denommetadata/types/packet_metadata.go | 58 ++ x/rollapp/keeper/keeper.go | 20 +- x/rollapp/transfergenesis/ibc_module.go | 1 + x/transferinject/doc.go | 2 - x/transferinject/ibc_module.go | 75 --- x/transferinject/ibc_module_test.go | 198 ------- x/transferinject/ics4_wrapper.go | 97 --- x/transferinject/ics4_wrapper_test.go | 229 ------- x/transferinject/types/codec.go | 8 - x/transferinject/types/expected_keepers.go | 21 - x/transferinject/types/packet_metadata.go | 79 --- x/transferinject/util_test.go | 28 - 25 files changed, 1023 insertions(+), 817 deletions(-) create mode 100644 x/denommetadata/ibc_middleware.go create mode 100644 x/denommetadata/ibc_middleware_test.go delete mode 100644 x/denommetadata/types/errors.go create mode 100644 x/denommetadata/types/packet_metadata.go delete mode 100644 x/transferinject/doc.go delete mode 100644 x/transferinject/ibc_module.go delete mode 100644 x/transferinject/ibc_module_test.go delete mode 100644 x/transferinject/ics4_wrapper.go delete mode 100644 x/transferinject/ics4_wrapper_test.go delete mode 100644 x/transferinject/types/codec.go delete mode 100644 x/transferinject/types/expected_keepers.go delete mode 100644 x/transferinject/types/packet_metadata.go delete mode 100644 x/transferinject/util_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dc35886..0fbfebe18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +- (denommetadata) [#955](https://github.com/dymensionxyz/dymension/issues/955) Add IBC middleware to create denom metadata from rollapp, on IBC transfer. - (genesisbridge) [#932](https://github.com/dymensionxyz/dymension/issues/932) Adds ibc module and ante handler to stop transfers to/from rollapp that has an incomplete genesis bridge (transfersEnabled) - (genesisbridge) [#932](https://github.com/dymensionxyz/dymension/issues/932) Adds a new temporary ibc module to set the canonical channel id, since we no longer do that using a whitelisted addr - (genesisbridge) [#932](https://github.com/dymensionxyz/dymension/issues/932) Adds a new ibc module to handle incoming 'genesis transfers'. It validates the special memo and registers a denom. It will not allow any regular transfers if transfers are not enabled diff --git a/app/app.go b/app/app.go index a284cc7e1..4e9242a32 100644 --- a/app/app.go +++ b/app/app.go @@ -147,8 +147,6 @@ import ( packetforwardkeeper "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6/packetforward/keeper" packetforwardtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6/packetforward/types" - "github.com/dymensionxyz/dymension/v3/x/transferinject" - /* ------------------------------ ethermint imports ----------------------------- */ "github.com/evmos/ethermint/ethereum/eip712" @@ -619,7 +617,7 @@ func New( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - transferinject.NewICS4Wrapper(app.IBCKeeper.ChannelKeeper, app.RollappKeeper, app.BankKeeper), + denommetadatamodule.NewICS4Wrapper(app.IBCKeeper.ChannelKeeper, app.RollappKeeper, app.BankKeeper), app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, @@ -644,7 +642,6 @@ func New( app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.EIBCKeeper, - app.BankKeeper, ) app.EIBCKeeper.SetDelayedAckKeeper(app.DelayedAckKeeper) @@ -737,9 +734,9 @@ func New( transferStack = bridgingfee.NewIBCModule(transferStack.(ibctransfer.IBCModule), app.DelayedAckKeeper, app.TransferKeeper, app.AccountKeeper.GetModuleAddress(txfeestypes.ModuleName), app.RollappKeeper) transferStack = packetforwardmiddleware.NewIBCMiddleware(transferStack, app.PacketForwardMiddlewareKeeper, 0, packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp) + transferStack = denommetadatamodule.NewIBCModule(transferStack, app.DenomMetadataKeeper, app.RollappKeeper) delayedAckMiddleware := delayedackmodule.NewIBCMiddleware(transferStack, app.DelayedAckKeeper, app.RollappKeeper) transferStack = delayedAckMiddleware - transferStack = transferinject.NewIBCModule(transferStack, app.RollappKeeper) transferStack = transfergenesis.NewIBCModule(transferStack, app.DelayedAckKeeper, app.RollappKeeper, app.TransferKeeper, app.DenomMetadataKeeper) transferStack = transfergenesis.NewIBCModuleCanonicalChannelHack(transferStack, app.RollappKeeper, app.IBCKeeper.ChannelKeeper) diff --git a/testutil/keeper/delayedack.go b/testutil/keeper/delayedack.go index 2590bec68..6d5c6b815 100644 --- a/testutil/keeper/delayedack.go +++ b/testutil/keeper/delayedack.go @@ -131,7 +131,14 @@ func DelayedackKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { "DelayedackParams", ) - k := keeper.NewKeeper(cdc, storeKey, paramsSubspace, RollappKeeperStub{}, ICS4WrapperStub{}, ChannelKeeperStub{}, nil, nil) + k := keeper.NewKeeper(cdc, + storeKey, + paramsSubspace, + RollappKeeperStub{}, + ICS4WrapperStub{}, + ChannelKeeperStub{}, + nil, + ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) diff --git a/x/delayedack/keeper/keeper.go b/x/delayedack/keeper/keeper.go index 2b0a3d444..d13273d32 100644 --- a/x/delayedack/keeper/keeper.go +++ b/x/delayedack/keeper/keeper.go @@ -15,22 +15,27 @@ import ( rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) -type ( - Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - hooks types.MultiDelayedAckHooks - paramstore paramtypes.Subspace +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + hooks types.MultiDelayedAckHooks + paramstore paramtypes.Subspace - rollappKeeper types.RollappKeeper - porttypes.ICS4Wrapper - channelKeeper types.ChannelKeeper - types.EIBCKeeper - bankKeeper types.BankKeeper - } -) + rollappKeeper types.RollappKeeper + porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + types.EIBCKeeper +} -func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ps paramtypes.Subspace, rollappKeeper types.RollappKeeper, ics4Wrapper porttypes.ICS4Wrapper, channelKeeper types.ChannelKeeper, eibcKeeper types.EIBCKeeper, bankKeeper types.BankKeeper) *Keeper { +func NewKeeper( + cdc codec.BinaryCodec, + storeKey storetypes.StoreKey, + ps paramtypes.Subspace, + rollappKeeper types.RollappKeeper, + ics4Wrapper porttypes.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + eibcKeeper types.EIBCKeeper, +) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { ps = ps.WithKeyTable(types.ParamKeyTable()) @@ -42,7 +47,6 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ps paramtype rollappKeeper: rollappKeeper, ICS4Wrapper: ics4Wrapper, channelKeeper: channelKeeper, - bankKeeper: bankKeeper, EIBCKeeper: eibcKeeper, } } diff --git a/x/delayedack/types/expected_keepers.go b/x/delayedack/types/expected_keepers.go index 16d7c131f..ee400ada1 100644 --- a/x/delayedack/types/expected_keepers.go +++ b/x/delayedack/types/expected_keepers.go @@ -4,8 +4,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + commontypes "github.com/dymensionxyz/dymension/v3/x/common/types" - eibctypes "github.com/dymensionxyz/dymension/v3/x/eibc/types" "github.com/dymensionxyz/dymension/v3/x/rollapp/types" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) @@ -29,12 +29,5 @@ type RollappKeeper interface { } type EIBCKeeper interface { - SetDemandOrder(ctx sdk.Context, order *eibctypes.DemandOrder) error - TimeoutFee(ctx sdk.Context) sdk.Dec - ErrAckFee(ctx sdk.Context) sdk.Dec EIBCDemandOrderHandler(ctx sdk.Context, rollappPacket commontypes.RollappPacket, data transfertypes.FungibleTokenPacketData) error } - -type BankKeeper interface { - BlockedAddr(addr sdk.AccAddress) bool -} diff --git a/x/denommetadata/ibc_middleware.go b/x/denommetadata/ibc_middleware.go new file mode 100644 index 000000000..b8fca9633 --- /dev/null +++ b/x/denommetadata/ibc_middleware.go @@ -0,0 +1,238 @@ +package denommetadata + +import ( + . "slices" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + "github.com/dymensionxyz/sdk-utils/utils/uibc" + + commontypes "github.com/dymensionxyz/dymension/v3/x/common/types" + "github.com/dymensionxyz/dymension/v3/x/denommetadata/types" +) + +var _ porttypes.IBCModule = &IBCModule{} + +// IBCModule implements the ICS26 callbacks for the transfer middleware +type IBCModule struct { + porttypes.IBCModule + keeper types.DenomMetadataKeeper + rollappKeeper types.RollappKeeper +} + +// NewIBCModule creates a new IBCModule given the keepers and underlying application +func NewIBCModule( + app porttypes.IBCModule, + keeper types.DenomMetadataKeeper, + rollappKeeper types.RollappKeeper, +) IBCModule { + return IBCModule{ + IBCModule: app, + keeper: keeper, + rollappKeeper: rollappKeeper, + } +} + +// OnRecvPacket registers the denom metadata if it does not exist. +// It will intercept an incoming packet and check if the denom metadata exists. +// If it does not, it will register the denom metadata. +// The handler will expect a 'denom_metadata' object in the memo field of the packet. +// If the memo is not an object, or does not contain the metadata, it moves on to the next handler. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) exported.Acknowledgement { + if commontypes.SkipRollappMiddleware(ctx) { + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) + } + + transferData, err := im.rollappKeeper.GetValidTransfer(ctx, packet.Data, packet.DestinationPort, packet.DestinationChannel) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + rollapp, packetData := transferData.Rollapp, transferData.FungibleTokenPacketData + + if packetData.Memo == "" { + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) + } + + // at this point it's safe to assume that we are not handling a native token of the rollapp + denomTrace := uibc.GetForeignDenomTrace(packet.GetDestChannel(), packetData.Denom) + ibcDenom := denomTrace.IBCDenom() + + dm := types.ParsePacketMetadata(packetData.Memo) + if dm == nil { + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) + } + + if err = dm.Validate(); err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + if dm.Base != packetData.Denom { + return channeltypes.NewErrorAcknowledgement(gerrc.ErrInvalidArgument) + } + + // if denom metadata was found in the memo, it means we should have the rollapp record + if rollapp == nil { + return channeltypes.NewErrorAcknowledgement(gerrc.ErrNotFound) + } + + dm.Base = ibcDenom + dm.DenomUnits[0].Denom = dm.Base + + if err = im.keeper.CreateDenomMetadata(ctx, *dm); err != nil { + if errorsmod.IsOf(err, gerrc.ErrAlreadyExists) { + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) + } + return channeltypes.NewErrorAcknowledgement(err) + } + + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) +} + +// OnAcknowledgementPacket adds the token metadata to the rollapp if it doesn't exist +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + var ack channeltypes.Acknowledgement + if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return errorsmod.Wrapf(errortypes.ErrJSONUnmarshal, "unmarshal ICS-20 transfer packet acknowledgement: %v", err) + } + + if !ack.Success() { + return im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + transferData, err := im.rollappKeeper.GetValidTransfer(ctx, packet.Data, packet.DestinationPort, packet.DestinationChannel) + if err != nil { + return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "get valid transfer data: %s", err.Error()) + } + + rollapp, packetData := transferData.Rollapp, transferData.FungibleTokenPacketData + + if packetData.Memo == "" { + return im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + dm := types.ParsePacketMetadata(packetData.Memo) + if dm == nil { + return im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + if err = dm.Validate(); err != nil { + return im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + } + + // if denom metadata was found in the memo, it means we should have the rollapp record + if rollapp == nil { + return gerrc.ErrNotFound + } + + if !Contains(rollapp.RegisteredDenoms, dm.Base) { + // add the new token denom base to the list of rollapp's registered denoms + rollapp.RegisteredDenoms = append(rollapp.RegisteredDenoms, dm.Base) + + im.rollappKeeper.SetRollapp(ctx, *rollapp) + } + + return im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +// ICS4Wrapper intercepts outgoing IBC packets and adds token metadata to the memo if the rollapp doesn't have it. +// This is a solution for adding token metadata to fungible tokens transferred over IBC, +// targeted at rollapps that don't have the token metadata for the token being transferred. +// More info here: https://www.notion.so/dymension/ADR-x-IBC-Denom-Metadata-Transfer-From-Hub-to-Rollapp-d3791f524ac849a9a3eb44d17968a30b +type ICS4Wrapper struct { + porttypes.ICS4Wrapper + + rollappKeeper types.RollappKeeper + bankKeeper types.BankKeeper +} + +// NewICS4Wrapper creates a new ICS4Wrapper +func NewICS4Wrapper( + ics porttypes.ICS4Wrapper, + rollappKeeper types.RollappKeeper, + bankKeeper types.BankKeeper, +) *ICS4Wrapper { + return &ICS4Wrapper{ + ICS4Wrapper: ics, + rollappKeeper: rollappKeeper, + bankKeeper: bankKeeper, + } +} + +// SendPacket wraps IBC ChannelKeeper's SendPacket function +func (m *ICS4Wrapper) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + destinationPort string, destinationChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (sequence uint64, err error) { + packet := new(transfertypes.FungibleTokenPacketData) + if err = types.ModuleCdc.UnmarshalJSON(data, packet); err != nil { + return 0, errorsmod.Wrapf(errortypes.ErrJSONUnmarshal, "unmarshal ICS-20 transfer packet data: %s", err.Error()) + } + + if types.MemoHasPacketMetadata(packet.Memo) { + return 0, types.ErrMemoDenomMetadataAlreadyExists + } + + transferData, err := m.rollappKeeper.GetValidTransfer(ctx, data, destinationPort, destinationChannel) + if err != nil { + return 0, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "get valid transfer data: %s", err.Error()) + } + + rollapp := transferData.Rollapp + // TODO: currently we check if receiving chain is a rollapp, consider that other chains also might want this feature + // meaning, find a better way to check if the receiving chain supports this middleware + if rollapp == nil { + return m.ICS4Wrapper.SendPacket(ctx, chanCap, destinationPort, destinationChannel, timeoutHeight, timeoutTimestamp, data) + } + + if transfertypes.ReceiverChainIsSource(destinationPort, destinationChannel, packet.Denom) { + return m.ICS4Wrapper.SendPacket(ctx, chanCap, destinationPort, destinationChannel, timeoutHeight, timeoutTimestamp, data) + } + + // Check if the rollapp already contains the denom metadata by matching the base of the denom metadata. + // At the first match, we assume that the rollapp already contains the metadata. + // It would be technically possible to have a race condition where the denom metadata is added to the rollapp + // from another packet before this packet is acknowledged. + if Contains(rollapp.RegisteredDenoms, packet.Denom) { + return m.ICS4Wrapper.SendPacket(ctx, chanCap, destinationPort, destinationChannel, timeoutHeight, timeoutTimestamp, data) + } + + // get the denom metadata from the bank keeper, if it doesn't exist, move on to the next middleware in the chain + denomMetadata, ok := m.bankKeeper.GetDenomMetaData(ctx, packet.Denom) + if !ok { + return m.ICS4Wrapper.SendPacket(ctx, chanCap, destinationPort, destinationChannel, timeoutHeight, timeoutTimestamp, data) + } + + packet.Memo, err = types.AddDenomMetadataToMemo(packet.Memo, denomMetadata) + if err != nil { + return 0, errorsmod.Wrapf(gerrc.ErrInvalidArgument, "add denom metadata to memo: %s", err.Error()) + } + + data, err = types.ModuleCdc.MarshalJSON(packet) + if err != nil { + return 0, errorsmod.Wrapf(errortypes.ErrJSONMarshal, "marshal ICS-20 transfer packet data: %s", err.Error()) + } + + return m.ICS4Wrapper.SendPacket(ctx, chanCap, destinationPort, destinationChannel, timeoutHeight, timeoutTimestamp, data) +} diff --git a/x/denommetadata/ibc_middleware_test.go b/x/denommetadata/ibc_middleware_test.go new file mode 100644 index 000000000..3a0b127ff --- /dev/null +++ b/x/denommetadata/ibc_middleware_test.go @@ -0,0 +1,655 @@ +package denommetadata_test + +import ( + "encoding/json" + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/dymensionxyz/dymension/v3/x/denommetadata" + "github.com/dymensionxyz/dymension/v3/x/denommetadata/types" + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" +) + +func TestIBCModule_OnRecvPacket(t *testing.T) { + tests := []struct { + name string + keeper *mockDenomMetadataKeeper + rollappKeeper *mockRollappKeeper + + memoData *memoData + wantAck exported.Acknowledgement + wantSentMemoData *memoData + wantCreated bool + }{ + { + name: "valid packet data with packet metadata", + keeper: &mockDenomMetadataKeeper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: validMemoData, + wantAck: emptyResult, + wantSentMemoData: nil, + wantCreated: true, + }, { + name: "valid packet data with packet metadata and user memo", + keeper: &mockDenomMetadataKeeper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: validMemoDataWithUserMemo, + wantAck: emptyResult, + wantSentMemoData: validUserMemo, + wantCreated: true, + }, { + name: "no memo", + keeper: &mockDenomMetadataKeeper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: nil, + wantAck: emptyResult, + wantSentMemoData: nil, + wantCreated: false, + }, { + name: "custom memo", + keeper: &mockDenomMetadataKeeper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: validUserMemo, + wantAck: emptyResult, + wantSentMemoData: validUserMemo, + wantCreated: false, + }, { + name: "memo has empty denom metadata", + keeper: &mockDenomMetadataKeeper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: invalidMemoDataNoDenomMetadata, + wantAck: emptyResult, + wantSentMemoData: nil, + wantCreated: false, + }, { + name: "denom metadata already exists in keeper", + keeper: &mockDenomMetadataKeeper{hasDenomMetaData: true}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + memoData: validMemoData, + wantAck: emptyResult, + wantSentMemoData: nil, + wantCreated: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + app := &mockIBCModule{} + im := denommetadata.NewIBCModule(app, tt.keeper, tt.rollappKeeper) + var memo string + if tt.memoData != nil { + memo = mustMarshalJSON(tt.memoData) + } + packetData := packetDataWithMemo(memo) + tt.rollappKeeper.packetData = packetData + packetDataBytes := types.ModuleCdc.MustMarshalJSON(&packetData) + packet := channeltypes.Packet{Data: packetDataBytes, SourcePort: "transfer", SourceChannel: "channel-0"} + got := im.OnRecvPacket(sdk.NewContext(nil, tmproto.Header{}, false, nil), packet, sdk.AccAddress{}) + require.Equal(t, tt.wantAck, got) + if !tt.wantAck.Success() { + return + } + var wantMemo string + if tt.wantSentMemoData != nil { + wantMemo = mustMarshalJSON(tt.wantSentMemoData) + } + wantPacketData := packetDataWithMemo(wantMemo) + wantPacketDataBytes := types.ModuleCdc.MustMarshalJSON(&wantPacketData) + require.Equal(t, string(wantPacketDataBytes), string(app.sentData)) + require.Equal(t, tt.wantCreated, tt.keeper.created) + }) + } +} + +func TestICS4Wrapper_SendPacket(t *testing.T) { + type fields struct { + ICS4Wrapper porttypes.ICS4Wrapper + rollappKeeper types.RollappKeeper + bankKeeper types.BankKeeper + } + type args struct { + destinationPort string + destinationChannel string + data *transfertypes.FungibleTokenPacketData + } + tests := []struct { + name string + fields fields + args args + wantSentData []byte + wantErr error + }{ + { + name: "success: added denom metadata to memo", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + bankKeeper: mockBankKeeper{ + returnMetadata: validDenomMetadata, + }, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("", validDenomMetadata), + }), + }, { + name: "success: added denom metadata to non-empty user memo", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + bankKeeper: mockBankKeeper{ + returnMetadata: validDenomMetadata, + }, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: "thanks for the sweater, grandma!", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("thanks for the sweater, grandma!", validDenomMetadata), + }), + }, { + name: "error: denom metadata already in memo", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: `{"denom_metadata":{}}`, + }, + }, + wantSentData: []byte(""), + wantErr: types.ErrMemoDenomMetadataAlreadyExists, + }, { + name: "error: extract rollapp from channel", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + err: errortypes.ErrInvalidRequest, + }, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }, + }, + wantSentData: []byte(""), + wantErr: errortypes.ErrInvalidRequest, + }, { + name: "send unaltered: rollapp not found", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{}, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: "user memo", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: "user memo", + }), + }, { + name: "send unaltered: receiver chain is source", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + bankKeeper: mockBankKeeper{ + returnMetadata: validDenomMetadata, + }, + }, + args: args{ + destinationPort: "transfer", + destinationChannel: "channel-56", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-56/alex", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-56/alex", + }), + }, { + name: "send unaltered: denom metadata already in rollapp", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{ + RegisteredDenoms: []string{"adym"}, + }, + }, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }), + }, { + name: "error: get denom metadata", + fields: fields{ + ICS4Wrapper: &mockICS4Wrapper{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + bankKeeper: mockBankKeeper{}, + }, + args: args{ + destinationPort: "port", + destinationChannel: "channel", + data: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }, + }, + wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := denommetadata.NewICS4Wrapper(tt.fields.ICS4Wrapper, tt.fields.rollappKeeper, tt.fields.bankKeeper) + + data := types.ModuleCdc.MustMarshalJSON(tt.args.data) + + _, err := m.SendPacket( + sdk.Context{}, + &capabilitytypes.Capability{}, + tt.args.destinationPort, + tt.args.destinationChannel, + clienttypes.Height{}, + 0, + data, + ) + if tt.wantErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tt.wantErr) + } + require.Equal(t, string(tt.wantSentData), string(tt.fields.ICS4Wrapper.(*mockICS4Wrapper).sentData)) + }) + } +} + +func TestIBCModule_OnAcknowledgementPacket(t *testing.T) { + type fields struct { + IBCModule porttypes.IBCModule + rollappKeeper *mockRollappKeeper + } + type args struct { + packetData *transfertypes.FungibleTokenPacketData + acknowledgement []byte + } + tests := []struct { + name string + fields fields + args args + wantRollapp *rollapptypes.Rollapp + wantErr error + }{ + { + name: "success: added token metadata to rollapp", + fields: fields{ + IBCModule: &mockIBCModule{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("", validDenomMetadata), + }, + acknowledgement: okAck(), + }, + wantRollapp: &rollapptypes.Rollapp{ + RegisteredDenoms: []string{validDenomMetadata.Base}, + }, + }, { + name: "success: added token metadata to rollapp with user memo", + fields: fields{ + IBCModule: &mockIBCModule{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("user memo", validDenomMetadata), + }, + acknowledgement: okAck(), + }, + wantRollapp: &rollapptypes.Rollapp{ + RegisteredDenoms: []string{validDenomMetadata.Base}, + }, + }, { + name: "return early: error acknowledgement", + fields: fields{ + rollappKeeper: &mockRollappKeeper{}, + IBCModule: &mockIBCModule{}, + }, + args: args{ + acknowledgement: badAck(), + }, + wantRollapp: nil, + }, { + name: "return early: no memo", + fields: fields{ + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{}, + }, + IBCModule: &mockIBCModule{}, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + }, + acknowledgement: okAck(), + }, + wantRollapp: &rollapptypes.Rollapp{}, + }, { + name: "return early: no packet metadata in memo", + fields: fields{ + rollappKeeper: &mockRollappKeeper{}, + IBCModule: &mockIBCModule{}, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: "user memo", + }, + acknowledgement: okAck(), + }, + wantRollapp: nil, + }, { + name: "return early: no denom metadata in memo", + fields: fields{ + rollappKeeper: &mockRollappKeeper{}, + IBCModule: &mockIBCModule{}, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: `{"denom_metadata":{}}`, + }, + acknowledgement: okAck(), + }, + wantRollapp: nil, + }, { + name: "error: extract rollapp from channel", + fields: fields{ + IBCModule: &mockIBCModule{}, + rollappKeeper: &mockRollappKeeper{ + err: errortypes.ErrInvalidRequest, + }, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("", validDenomMetadata), + }, + acknowledgement: okAck(), + }, + wantRollapp: nil, + wantErr: errortypes.ErrInvalidRequest, + }, { + name: "error: rollapp not found", + fields: fields{ + IBCModule: &mockIBCModule{}, + rollappKeeper: &mockRollappKeeper{}, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("", validDenomMetadata), + }, + acknowledgement: okAck(), + }, + wantRollapp: nil, + wantErr: gerrc.ErrNotFound, + }, { + name: "return early: rollapp already has token metadata", + fields: fields{ + IBCModule: &mockIBCModule{}, + rollappKeeper: &mockRollappKeeper{ + returnRollapp: &rollapptypes.Rollapp{ + RegisteredDenoms: []string{validDenomMetadata.Base}, + }, + }, + }, + args: args{ + packetData: &transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Memo: addDenomMetadataToPacketData("", validDenomMetadata), + }, + acknowledgement: okAck(), + }, + wantRollapp: &rollapptypes.Rollapp{ + RegisteredDenoms: []string{validDenomMetadata.Base}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := denommetadata.NewIBCModule(tt.fields.IBCModule, nil, tt.fields.rollappKeeper) + + packet := channeltypes.Packet{} + + if tt.args.packetData != nil { + tt.fields.rollappKeeper.packetData = *tt.args.packetData + packet.Data = types.ModuleCdc.MustMarshalJSON(tt.args.packetData) + } + + err := m.OnAcknowledgementPacket(sdk.Context{}, packet, tt.args.acknowledgement, sdk.AccAddress{}) + + if tt.wantErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tt.wantErr) + } + + require.Equal(t, tt.wantRollapp, tt.fields.rollappKeeper.returnRollapp) + }) + } +} + +var ( + emptyResult = channeltypes.Acknowledgement{} + validUserMemo = &memoData{ + User: &validUserData, + } + validMemoDataWithUserMemo = &memoData{ + MemoData: validMemoData.MemoData, + User: &validUserData, + } + validUserData = userData{Data: "data"} + validMemoData = &memoData{ + MemoData: types.MemoData{ + DenomMetadata: &validDenomMetadata, + }, + } + invalidMemoDataNoDenomMetadata = &memoData{ + MemoData: types.MemoData{}, + } + validDenomMetadata = banktypes.Metadata{ + Description: "Denom of the Hub", + Base: "adym", + Display: "DYM", + Name: "DYM", + Symbol: "adym", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "adym", + Exponent: 0, + }, { + Denom: "DYM", + Exponent: 18, + }, + }, + } +) + +type memoData struct { + types.MemoData + User *userData `json:"user,omitempty"` +} + +type userData struct { + Data string `json:"data"` +} + +func packetDataWithMemo(memo string) transfertypes.FungibleTokenPacketData { + return transfertypes.FungibleTokenPacketData{ + Denom: "adym", + Amount: "100", + Sender: "sender", + Receiver: "receiver", + Memo: memo, + } +} + +func addDenomMetadataToPacketData(memo string, metadata banktypes.Metadata) string { + memo, _ = types.AddDenomMetadataToMemo(memo, metadata) + return memo +} + +func mustMarshalJSON(v any) string { + bz, err := json.Marshal(v) + if err != nil { + panic(err) + } + return string(bz) +} + +type mockIBCModule struct { + porttypes.IBCModule + sentData []byte +} + +func okAck() []byte { + ack := channeltypes.NewResultAcknowledgement([]byte{}) + return types.ModuleCdc.MustMarshalJSON(&ack) +} + +func badAck() []byte { + ack := channeltypes.NewErrorAcknowledgement(fmt.Errorf("unsuccessful")) + return types.ModuleCdc.MustMarshalJSON(&ack) +} + +func (m *mockIBCModule) OnRecvPacket(_ sdk.Context, p channeltypes.Packet, _ sdk.AccAddress) exported.Acknowledgement { + m.sentData = p.Data + return emptyResult +} + +func (m *mockIBCModule) OnAcknowledgementPacket(_ sdk.Context, _ channeltypes.Packet, ack []byte, _ sdk.AccAddress) error { + return nil +} + +type mockDenomMetadataKeeper struct { + hasDenomMetaData, created bool +} + +func (m *mockDenomMetadataKeeper) CreateDenomMetadata(ctx sdk.Context, metadata banktypes.Metadata) error { + m.created = true + return nil +} + +type mockICS4Wrapper struct { + porttypes.ICS4Wrapper + sentData []byte +} + +func (m *mockICS4Wrapper) SendPacket( + _ sdk.Context, + _ *capabilitytypes.Capability, + _ string, _ string, + _ clienttypes.Height, + _ uint64, + data []byte, +) (sequence uint64, err error) { + m.sentData = data + return 0, nil +} + +type mockRollappKeeper struct { + returnRollapp *rollapptypes.Rollapp + packetData transfertypes.FungibleTokenPacketData + err error +} + +func (m *mockRollappKeeper) SetRollapp(_ sdk.Context, rollapp rollapptypes.Rollapp) { + m.returnRollapp = &rollapp +} + +func (m *mockRollappKeeper) GetValidTransfer(sdk.Context, []byte, string, string) (data rollapptypes.TransferData, err error) { + return rollapptypes.TransferData{ + Rollapp: m.returnRollapp, + FungibleTokenPacketData: m.packetData, + }, m.err +} + +type mockBankKeeper struct { + returnMetadata banktypes.Metadata +} + +func (m mockBankKeeper) SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) { +} + +func (m mockBankKeeper) GetDenomMetaData(sdk.Context, string) (banktypes.Metadata, bool) { + return m.returnMetadata, m.returnMetadata.Base != "" +} diff --git a/x/denommetadata/keeper/keeper.go b/x/denommetadata/keeper/keeper.go index b1e6dedf5..8052c817b 100644 --- a/x/denommetadata/keeper/keeper.go +++ b/x/denommetadata/keeper/keeper.go @@ -3,13 +3,13 @@ package keeper import ( "fmt" - "github.com/dymensionxyz/dymension/v3/x/denommetadata/types" - "github.com/dymensionxyz/gerr-cosmos/gerrc" - + sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/tendermint/tendermint/libs/log" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + + "github.com/dymensionxyz/dymension/v3/x/denommetadata/types" ) // Keeper of the denommetadata store @@ -27,7 +27,8 @@ func NewKeeper(bankKeeper types.BankKeeper) *Keeper { } func (k *Keeper) HasDenomMetadata(ctx sdk.Context, base string) bool { - return k.bankKeeper.HasDenomMetaData(ctx, base) + _, found := k.bankKeeper.GetDenomMetaData(ctx, base) + return found } // CreateDenomMetadata creates a new denommetadata @@ -58,7 +59,7 @@ func (k *Keeper) UpdateDenomMetadata(ctx sdk.Context, metadata banktypes.Metadat return nil } -func (k Keeper) Logger(ctx sdk.Context) log.Logger { +func (k *Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } diff --git a/x/denommetadata/keeper/keeper_test.go b/x/denommetadata/keeper/keeper_test.go index 31f99787c..3e8f1f4d4 100644 --- a/x/denommetadata/keeper/keeper_test.go +++ b/x/denommetadata/keeper/keeper_test.go @@ -6,10 +6,10 @@ import ( errorsmod "cosmossdk.io/errors" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/dymensionxyz/gerr-cosmos/gerrc" - - "github.com/dymensionxyz/dymension/v3/app/apptesting" "github.com/stretchr/testify/suite" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/dymensionxyz/dymension/v3/app/apptesting" ) type KeeperTestSuite struct { diff --git a/x/denommetadata/proposal_handler.go b/x/denommetadata/proposal_handler.go index 91e6c3d44..f4a77f572 100644 --- a/x/denommetadata/proposal_handler.go +++ b/x/denommetadata/proposal_handler.go @@ -4,6 +4,8 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/dymensionxyz/gerr-cosmos/gerrc" + "github.com/dymensionxyz/dymension/v3/x/denommetadata/keeper" "github.com/dymensionxyz/dymension/v3/x/denommetadata/types" ) @@ -16,7 +18,7 @@ func NewDenomMetadataProposalHandler(k *keeper.Keeper) govtypes.Handler { case *types.UpdateDenomMetadataProposal: return HandleUpdateDenomMetadataProposal(ctx, k, c) default: - return errorsmod.Wrapf(types.ErrUnknownRequest, "unrecognized denommetadata proposal content type: %T", c) + return errorsmod.WithType(gerrc.ErrInvalidArgument, c) } } } diff --git a/x/denommetadata/types/codec.go b/x/denommetadata/types/codec.go index a5b12789d..6b13ba912 100644 --- a/x/denommetadata/types/codec.go +++ b/x/denommetadata/types/codec.go @@ -3,20 +3,14 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" ) -var ( - amino = codec.NewLegacyAmino() - ModuleCdc = codec.NewAminoCodec(amino) -) +var ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) // RegisterCodec registers the necessary x/denommetadata interfaces and concrete types on the provided // LegacyAmino codec. These types are used for Amino JSON serialization. -func RegisterCodec(cdc *codec.LegacyAmino) { -} +func RegisterCodec(*codec.LegacyAmino) {} // RegisterInterfaces registers interfaces and implementations of the denommetadata module. func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -26,13 +20,3 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &UpdateDenomMetadataProposal{}, ) } - -func init() { - RegisterCodec(amino) - // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be - // used to properly serialize MsgGrant and MsgExec instances - sdk.RegisterLegacyAminoCodec(amino) - RegisterCodec(authzcodec.Amino) - - amino.Seal() -} diff --git a/x/denommetadata/types/errors.go b/x/denommetadata/types/errors.go deleted file mode 100644 index d96c7738b..000000000 --- a/x/denommetadata/types/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package types - -// DONTCOVER - -import ( - errorsmod "cosmossdk.io/errors" -) - -// x/denommetadata module sentinel errors -var ( - ErrUnknownRequest = errorsmod.Register(ModuleName, 1002, "unknown request") -) diff --git a/x/denommetadata/types/expected_keepers.go b/x/denommetadata/types/expected_keepers.go index 0881e1d77..421edfe58 100644 --- a/x/denommetadata/types/expected_keepers.go +++ b/x/denommetadata/types/expected_keepers.go @@ -3,9 +3,25 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank/types" + + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) +// BankKeeper defines the expected interface needed type BankKeeper interface { - HasDenomMetaData(ctx sdk.Context, denom string) bool + GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) } + +type DenomMetadataKeeper interface { + CreateDenomMetadata(ctx sdk.Context, metadata types.Metadata) error +} + +type RollappKeeper interface { + SetRollapp(ctx sdk.Context, rollapp rollapptypes.Rollapp) + GetValidTransfer( + ctx sdk.Context, + packetData []byte, + raPortOnHub, raChanOnHub string, + ) (data rollapptypes.TransferData, err error) +} diff --git a/x/denommetadata/types/packet_metadata.go b/x/denommetadata/types/packet_metadata.go new file mode 100644 index 000000000..bf5b0fcea --- /dev/null +++ b/x/denommetadata/types/packet_metadata.go @@ -0,0 +1,58 @@ +package types + +import ( + "encoding/json" + + errorsmod "cosmossdk.io/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// MemoData represents the structure of the memo with user and hub metadata +type MemoData struct { + DenomMetadata *types.Metadata `json:"denom_metadata,omitempty"` +} + +func (p MemoData) ValidateBasic() error { + return p.DenomMetadata.Validate() +} + +const memoObjectKeyDenomMetadata = "denom_metadata" + +var ErrMemoDenomMetadataAlreadyExists = errorsmod.Wrapf(errortypes.ErrUnauthorized, "'denom_metadata' already exists in memo") + +func ParsePacketMetadata(input string) *types.Metadata { + bz := []byte(input) + var memo MemoData + _ = json.Unmarshal(bz, &memo) // we don't care about the error + return memo.DenomMetadata +} + +func MemoHasPacketMetadata(memo string) bool { + memoMap := make(map[string]any) + err := json.Unmarshal([]byte(memo), &memoMap) + if err != nil { + return false + } + + _, ok := memoMap[memoObjectKeyDenomMetadata] + return ok +} + +func AddDenomMetadataToMemo(memo string, denomMetadata types.Metadata) (string, error) { + memoMap := make(map[string]any) + // doesn't matter if there is an error, the memo can be empty + _ = json.Unmarshal([]byte(memo), &memoMap) + + if _, ok := memoMap[memoObjectKeyDenomMetadata]; ok { + return "", ErrMemoDenomMetadataAlreadyExists + } + + memoMap[memoObjectKeyDenomMetadata] = &denomMetadata + bz, err := json.Marshal(memoMap) + if err != nil { + return memo, err + } + + return string(bz), nil +} diff --git a/x/rollapp/keeper/keeper.go b/x/rollapp/keeper/keeper.go index d502767b7..97d93f437 100644 --- a/x/rollapp/keeper/keeper.go +++ b/x/rollapp/keeper/keeper.go @@ -13,17 +13,15 @@ import ( "github.com/dymensionxyz/dymension/v3/x/rollapp/types" ) -type ( - Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - hooks types.MultiRollappHooks - paramstore paramtypes.Subspace - - ibcClientKeeper types.IBCClientKeeper - channelKeeper types.ChannelKeeper - } -) +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + hooks types.MultiRollappHooks + paramstore paramtypes.Subspace + + ibcClientKeeper types.IBCClientKeeper + channelKeeper types.ChannelKeeper +} func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ps paramtypes.Subspace, channelKeeper types.ChannelKeeper) *Keeper { // set KeyTable if it has not already been set diff --git a/x/rollapp/transfergenesis/ibc_module.go b/x/rollapp/transfergenesis/ibc_module.go index 01880a213..2c2ea4ca1 100644 --- a/x/rollapp/transfergenesis/ibc_module.go +++ b/x/rollapp/transfergenesis/ibc_module.go @@ -23,6 +23,7 @@ import ( channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" "github.com/cosmos/ibc-go/v6/modules/core/exported" + delayedackkeeper "github.com/dymensionxyz/dymension/v3/x/delayedack/keeper" rollappkeeper "github.com/dymensionxyz/dymension/v3/x/rollapp/keeper" rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" diff --git a/x/transferinject/doc.go b/x/transferinject/doc.go deleted file mode 100644 index 97f5847db..000000000 --- a/x/transferinject/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package transferinject module provides IBC middleware for sending and acknowledging IBC packets with injecting additional packet metadata to IBC packets. -package transferinject diff --git a/x/transferinject/ibc_module.go b/x/transferinject/ibc_module.go deleted file mode 100644 index 8b625f428..000000000 --- a/x/transferinject/ibc_module.go +++ /dev/null @@ -1,75 +0,0 @@ -// Package transferinject module provides IBC middleware for sending and acknowledging IBC packets with injecting additional packet metadata to IBC packets. -package transferinject - -import ( - "errors" - . "slices" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" - "github.com/dymensionxyz/gerr-cosmos/gerrc" - - "github.com/dymensionxyz/dymension/v3/x/transferinject/types" -) - -type IBCModule struct { - porttypes.IBCModule - - rollappKeeper types.RollappKeeper -} - -// NewIBCModule creates a new IBCModule. -// It intercepts acknowledged incoming IBC packets and adds token metadata that had just been registered on the rollapp itself, -// to the local rollapp record. -func NewIBCModule( - ibc porttypes.IBCModule, - rollappKeeper types.RollappKeeper, -) *IBCModule { - return &IBCModule{ - IBCModule: ibc, - rollappKeeper: rollappKeeper, - } -} - -// OnAcknowledgementPacket adds the token metadata to the rollapp if it doesn't exist -func (m *IBCModule) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - var ack channeltypes.Acknowledgement - if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { - return errorsmod.Wrap(errors.Join(err, errortypes.ErrJSONUnmarshal), "ics20 transfer packet ack") - } - - if !ack.Success() { - return m.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) - } - - transfer, err := m.rollappKeeper.GetValidTransfer(ctx, packet.GetData(), packet.GetSourcePort(), packet.GetSourceChannel()) - if err != nil { - return errorsmod.Wrap(err, "get valid transfer from sent packet") - } - - packetMetadata, err := types.ParsePacketMetadata(transfer.GetMemo()) - if err != nil { - return m.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) - } - - if !transfer.IsRollapp() { - return errorsmod.Wrap(errors.Join(gerrc.ErrNotFound, errortypes.ErrInvalidRequest), "got a memo so should get rollapp, but didnt") - } - - if !Contains(transfer.Rollapp.RegisteredDenoms, packetMetadata.DenomMetadata.Base) { - // add the new token denom base to the list of rollapp's registered denoms - transfer.Rollapp.RegisteredDenoms = append(transfer.Rollapp.RegisteredDenoms, packetMetadata.DenomMetadata.Base) - - m.rollappKeeper.SetRollapp(ctx, *transfer.Rollapp) - } - - return m.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) -} diff --git a/x/transferinject/ibc_module_test.go b/x/transferinject/ibc_module_test.go deleted file mode 100644 index af4503495..000000000 --- a/x/transferinject/ibc_module_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package transferinject_test - -import ( - "fmt" - "testing" - - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/dymensionxyz/gerr-cosmos/gerrc" - - sdk "github.com/cosmos/cosmos-sdk/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" - "github.com/stretchr/testify/require" - - rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" - "github.com/dymensionxyz/dymension/v3/x/transferinject" - "github.com/dymensionxyz/dymension/v3/x/transferinject/types" -) - -func TestIBCModule_OnAcknowledgementPacket(t *testing.T) { - type fields struct { - packetData *transfertypes.FungibleTokenPacketData - ack []byte - ibcModule porttypes.IBCModule - rollappKeeper *mockRollappKeeper - } - tests := []struct { - name string - fields fields - wantRollapp *rollapptypes.Rollapp - wantErr error - }{ - { - name: "success: added token metadata to rollapp", - fields: fields{ - ibcModule: mockIBCModule{}, - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("", validDenomMetadata), - }, - ack: okAck(), - }, - wantRollapp: &rollapptypes.Rollapp{ - RegisteredDenoms: []string{validDenomMetadata.Base}, - }, - }, { - name: "success: added token metadata to rollapp with user memo", - fields: fields{ - ibcModule: mockIBCModule{}, - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("user memo", validDenomMetadata), - }, - ack: okAck(), - }, - wantRollapp: &rollapptypes.Rollapp{ - RegisteredDenoms: []string{validDenomMetadata.Base}, - }, - }, { - name: "return early: error ack", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - ibcModule: mockIBCModule{}, - ack: errAck(), - }, - wantRollapp: nil, - }, { - name: "return early: no memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - ibcModule: mockIBCModule{}, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }, - ack: okAck(), - }, - wantRollapp: nil, - }, { - name: "return early: no packet metadata in memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - ibcModule: mockIBCModule{}, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: "user memo", - }, - ack: okAck(), - }, - wantRollapp: nil, - }, { - name: "return early: no denom metadata in memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - ibcModule: mockIBCModule{}, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: `{"transferinject":{}}`, - }, - ack: okAck(), - }, - wantRollapp: nil, - }, { - name: "error: extract rollapp from channel", - fields: fields{ - ibcModule: mockIBCModule{}, - rollappKeeper: &mockRollappKeeper{ - err: errortypes.ErrInvalidRequest, - }, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("", validDenomMetadata), - }, - ack: okAck(), - }, - wantRollapp: nil, - wantErr: errortypes.ErrInvalidRequest, - }, { - name: "error: rollapp not found", - fields: fields{ - ibcModule: mockIBCModule{}, - rollappKeeper: &mockRollappKeeper{}, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("", validDenomMetadata), - }, - ack: okAck(), - }, - wantRollapp: nil, - wantErr: gerrc.ErrNotFound, - }, { - name: "return early: rollapp already has token metadata", - fields: fields{ - ibcModule: mockIBCModule{}, - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{ - RegisteredDenoms: []string{validDenomMetadata.Base}, - }, - }, - packetData: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("", validDenomMetadata), - }, - ack: okAck(), - }, - wantRollapp: &rollapptypes.Rollapp{ - RegisteredDenoms: []string{validDenomMetadata.Base}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.fields.rollappKeeper.transfer = tt.fields.packetData - - m := transferinject.NewIBCModule(tt.fields.ibcModule, tt.fields.rollappKeeper) - - packet := channeltypes.Packet{} - - if tt.fields.packetData != nil { - packet.Data = types.ModuleCdc.MustMarshalJSON(tt.fields.packetData) - } - - err := m.OnAcknowledgementPacket(sdk.Context{}, packet, tt.fields.ack, sdk.AccAddress{}) - - if tt.wantErr == nil { - require.NoError(t, err) - } else { - require.ErrorIs(t, err, tt.wantErr) - } - - require.Equal(t, tt.wantRollapp, tt.fields.rollappKeeper.rollapp) - }) - } -} - -func okAck() []byte { - ack := channeltypes.NewResultAcknowledgement([]byte{}) - return types.ModuleCdc.MustMarshalJSON(&ack) -} - -func errAck() []byte { - ack := channeltypes.NewErrorAcknowledgement(fmt.Errorf("unsuccessful")) - return types.ModuleCdc.MustMarshalJSON(&ack) -} - -type mockIBCModule struct { - porttypes.IBCModule -} - -func (m mockIBCModule) OnAcknowledgementPacket(sdk.Context, channeltypes.Packet, []byte, sdk.AccAddress) error { - return nil -} diff --git a/x/transferinject/ics4_wrapper.go b/x/transferinject/ics4_wrapper.go deleted file mode 100644 index 96e1b88bc..000000000 --- a/x/transferinject/ics4_wrapper.go +++ /dev/null @@ -1,97 +0,0 @@ -package transferinject - -import ( - "errors" - . "slices" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" - porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" - "github.com/dymensionxyz/gerr-cosmos/gerrc" - - "github.com/dymensionxyz/dymension/v3/x/transferinject/types" -) - -type ICS4Wrapper struct { - porttypes.ICS4Wrapper - - rollappKeeper types.RollappKeeper - bankKeeper types.BankKeeper -} - -// NewICS4Wrapper creates a new ICS4Wrapper. -// It intercepts outgoing IBC packets and adds token metadata to the memo if the rollapp doesn't have it. -// This is a solution for adding token metadata to fungible tokens transferred over IBC, -// targeted at rollapps that don't have the token metadata for the token being transferred. -// More info here: https://www.notion.so/dymension/ADR-x-IBC-Denom-Metadata-Transfer-From-Hub-to-Rollapp-d3791f524ac849a9a3eb44d17968a30b -func NewICS4Wrapper( - next porttypes.ICS4Wrapper, - rollappKeeper types.RollappKeeper, - bankKeeper types.BankKeeper, -) *ICS4Wrapper { - return &ICS4Wrapper{ - ICS4Wrapper: next, - rollappKeeper: rollappKeeper, - bankKeeper: bankKeeper, - } -} - -// SendPacket wraps IBC ChannelKeeper's SendPacket function -func (m *ICS4Wrapper) SendPacket( - ctx sdk.Context, - chanCap *capabilitytypes.Capability, - srcPort string, srcChan string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - data []byte, -) (sequence uint64, err error) { - transfer, err := m.rollappKeeper.GetValidTransfer(ctx, data, srcPort, srcChan) - if err != nil { - return 0, errorsmod.Wrap(err, "transfer inject: get valid transfer") - } - - if types.MemoAlreadyHasPacketMetadata(transfer.GetMemo()) { - return 0, types.ErrMemoTransferInjectAlreadyExists - } - - if - // TODO: currently we check if receiving chain is a rollapp, consider that other chains also might want this feature - // meaning, find a better way to check if the receiving chain supports this middleware - !transfer.IsRollapp() || // proceed as normal - transfertypes.ReceiverChainIsSource(srcPort, srcChan, transfer.Denom) { - return m.ICS4Wrapper.SendPacket(ctx, chanCap, srcPort, srcChan, timeoutHeight, timeoutTimestamp, data) - } - - // Check if the rollapp already contains the denom metadata by matching the base of the denom metadata. - // At the first match, we assume that the rollapp already contains the metadata. - // It would be technically possible to have a race condition where the denom metadata is added to the rollapp - // from another packet before this packet is acknowledged. - if Contains(transfer.Rollapp.RegisteredDenoms, transfer.GetDenom()) { - return m.ICS4Wrapper.SendPacket(ctx, chanCap, srcPort, srcChan, timeoutHeight, timeoutTimestamp, data) - } - - // get the denom metadata from the bank keeper, if it doesn't exist, move on to the next middleware in the chain - denomMetadata, ok := m.bankKeeper.GetDenomMetaData(ctx, transfer.GetDenom()) - if !ok { - return m.ICS4Wrapper.SendPacket(ctx, chanCap, srcPort, srcChan, timeoutHeight, timeoutTimestamp, data) - } - - transfer.Memo, err = types.AddDenomMetadataToMemo(transfer.Memo, denomMetadata) - if err != nil { - if errors.Is(err, types.ErrMemoTransferInjectAlreadyExists) { - err = errors.Join(err, gerrc.ErrPermissionDenied) - } - return 0, errorsmod.Wrap(err, "transfer inject: add denom metadata to memo") - } - - data, err = types.ModuleCdc.MarshalJSON(&transfer.FungibleTokenPacketData) - if err != nil { - return 0, errorsmod.Wrap(errors.Join(err, errortypes.ErrJSONMarshal), "transfer inject: ics20 transfer packet data") - } - - return m.ICS4Wrapper.SendPacket(ctx, chanCap, srcPort, srcChan, timeoutHeight, timeoutTimestamp, data) -} diff --git a/x/transferinject/ics4_wrapper_test.go b/x/transferinject/ics4_wrapper_test.go deleted file mode 100644 index 309e760b7..000000000 --- a/x/transferinject/ics4_wrapper_test.go +++ /dev/null @@ -1,229 +0,0 @@ -package transferinject_test - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" - porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" - "github.com/stretchr/testify/require" - - rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" - "github.com/dymensionxyz/dymension/v3/x/transferinject" - "github.com/dymensionxyz/dymension/v3/x/transferinject/types" -) - -func TestICS4Wrapper_SendPacket(t *testing.T) { - type fields struct { - rollappKeeper *mockRollappKeeper - bankKeeper types.BankKeeper - srcPort string - srcChannel string - data *transfertypes.FungibleTokenPacketData - } - tests := []struct { - name string - fields fields - wantSentData []byte - wantErr error - }{ - { - name: "success: added denom metadata to memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - bankKeeper: mockBankKeeper{ - returnMetadata: validDenomMetadata, - }, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("", validDenomMetadata), - }), - }, { - name: "success: added denom metadata to non-empty user memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - bankKeeper: mockBankKeeper{ - returnMetadata: validDenomMetadata, - }, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: "thanks for the sweater, grandma!", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: addDenomMetadataToExistingMemo("thanks for the sweater, grandma!", validDenomMetadata), - }), - }, { - name: "error: denom metadata already in memo", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - bankKeeper: mockBankKeeper{}, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: `{"transferinject":{}}`, - }, - }, - wantSentData: []byte(""), - wantErr: types.ErrMemoTransferInjectAlreadyExists, - }, { - name: "send unaltered: rollapp not found", - fields: fields{ - rollappKeeper: &mockRollappKeeper{}, - bankKeeper: mockBankKeeper{}, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: "user memo", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "adym", - Memo: "user memo", - }), - }, { - name: "send unaltered: receiver chain is source", - fields: fields{ - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - bankKeeper: mockBankKeeper{ - returnMetadata: validDenomMetadata, - }, - srcPort: "transfer", - srcChannel: "channel-56", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "transfer/channel-56/alex", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "transfer/channel-56/alex", - }), - }, { - name: "send unaltered: denom metadata already in rollapp", - fields: fields{ - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{ - RegisteredDenoms: []string{"adym"}, - }, - }, - bankKeeper: mockBankKeeper{}, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }), - }, { - name: "error: get denom metadata", - fields: fields{ - rollappKeeper: &mockRollappKeeper{ - rollapp: &rollapptypes.Rollapp{}, - }, - bankKeeper: mockBankKeeper{}, - srcPort: "port", - srcChannel: "channel", - data: &transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }, - }, - wantSentData: types.ModuleCdc.MustMarshalJSON(&transfertypes.FungibleTokenPacketData{ - Denom: "adym", - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ics4 := mockICS4Wrapper{} - tt.fields.rollappKeeper.transfer = tt.fields.data - - m := transferinject.NewICS4Wrapper(&ics4, tt.fields.rollappKeeper, tt.fields.bankKeeper) - - data := types.ModuleCdc.MustMarshalJSON(tt.fields.data) - - _, err := m.SendPacket( - sdk.Context{}, - &capabilitytypes.Capability{}, - tt.fields.srcPort, - tt.fields.srcChannel, - clienttypes.Height{}, - 0, - data, - ) - if tt.wantErr == nil { - require.NoError(t, err) - } else { - require.ErrorIs(t, err, tt.wantErr) - } - require.Equal(t, string(tt.wantSentData), string(ics4.sentData)) - }) - } -} - -var validDenomMetadata = banktypes.Metadata{ - Description: "Denom of the Hub", - Base: "adym", - Display: "DYM", - Name: "DYM", - Symbol: "adym", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: "adym", - Exponent: 0, - }, { - Denom: "DYM", - Exponent: 18, - }, - }, -} - -func addDenomMetadataToExistingMemo(memo string, metadata banktypes.Metadata) string { - memo, _ = types.AddDenomMetadataToMemo(memo, metadata) - return memo -} - -type mockICS4Wrapper struct { - porttypes.ICS4Wrapper - sentData []byte -} - -func (m *mockICS4Wrapper) SendPacket( - _ sdk.Context, - _ *capabilitytypes.Capability, - _ string, _ string, - _ clienttypes.Height, - _ uint64, - data []byte, -) (sequence uint64, err error) { - m.sentData = data - return 0, nil -} - -type mockBankKeeper struct { - returnMetadata banktypes.Metadata -} - -func (m mockBankKeeper) GetDenomMetaData(sdk.Context, string) (banktypes.Metadata, bool) { - return m.returnMetadata, m.returnMetadata.Base != "" -} diff --git a/x/transferinject/types/codec.go b/x/transferinject/types/codec.go deleted file mode 100644 index e4d4b4af5..000000000 --- a/x/transferinject/types/codec.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" -) - -var ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) diff --git a/x/transferinject/types/expected_keepers.go b/x/transferinject/types/expected_keepers.go deleted file mode 100644 index 08d34f7da..000000000 --- a/x/transferinject/types/expected_keepers.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank/types" - rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" -) - -// BankKeeper defines the expected interface needed -type BankKeeper interface { - GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) -} - -type RollappKeeper interface { - SetRollapp(ctx sdk.Context, rollapp rollapptypes.Rollapp) - GetValidTransfer( - ctx sdk.Context, - packetData []byte, - raPortOnHub, raChanOnHub string, - ) (data rollapptypes.TransferData, err error) -} diff --git a/x/transferinject/types/packet_metadata.go b/x/transferinject/types/packet_metadata.go deleted file mode 100644 index 6a7fa5ec2..000000000 --- a/x/transferinject/types/packet_metadata.go +++ /dev/null @@ -1,79 +0,0 @@ -package types - -import ( - "encoding/json" - "fmt" - - errorsmod "cosmossdk.io/errors" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/dymensionxyz/gerr-cosmos/gerrc" -) - -// MemoData represents the structure of the memo with user and hub metadata -type MemoData struct { - TransferInject *TransferInject `json:"transferinject"` -} - -type TransferInject struct { - DenomMetadata *types.Metadata `json:"denom_metadata"` -} - -func (p TransferInject) ValidateBasic() error { - return p.DenomMetadata.Validate() -} - -const memoObjectKeyTransferInject = "transferinject" - -var ( - ErrMemoTransferInjectEmpty = fmt.Errorf("memo 'transferinject' is missing") - ErrMemoTransferInjectAlreadyExists = errorsmod.Wrapf(errortypes.ErrUnauthorized, "'transferinject' already exists in memo") -) - -func ParsePacketMetadata(input string) (*TransferInject, error) { - bz := []byte(input) - - var memo MemoData - if err := json.Unmarshal(bz, &memo); err != nil { - return nil, err - } - - if memo.TransferInject == nil { - return nil, ErrMemoTransferInjectEmpty - } - - if memo.TransferInject.DenomMetadata == nil { - return nil, errorsmod.Wrap(gerrc.ErrNotFound, "denom metadata") - } - - return memo.TransferInject, nil -} - -func MemoAlreadyHasPacketMetadata(memo string) bool { - memoMap := make(map[string]any) - err := json.Unmarshal([]byte(memo), &memoMap) - if err != nil { - return false - } - - _, ok := memoMap[memoObjectKeyTransferInject] - return ok -} - -func AddDenomMetadataToMemo(memo string, denomMetadata types.Metadata) (string, error) { - memoMap := make(map[string]any) - // doesn't matter if there is an error, the memo can be empty - _ = json.Unmarshal([]byte(memo), &memoMap) - - if _, ok := memoMap[memoObjectKeyTransferInject]; ok { - return "", ErrMemoTransferInjectAlreadyExists - } - - memoMap[memoObjectKeyTransferInject] = TransferInject{DenomMetadata: &denomMetadata} - bz, err := json.Marshal(memoMap) - if err != nil { - return memo, err - } - - return string(bz), nil -} diff --git a/x/transferinject/util_test.go b/x/transferinject/util_test.go deleted file mode 100644 index e48f47ad9..000000000 --- a/x/transferinject/util_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package transferinject_test - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" -) - -type mockRollappKeeper struct { - rollapp *rollapptypes.Rollapp - transfer *transfertypes.FungibleTokenPacketData - err error -} - -func (m *mockRollappKeeper) GetValidTransfer(ctx sdk.Context, packetData []byte, raPortOnHub, raChanOnHub string) (data rollapptypes.TransferData, err error) { - ret := rollapptypes.TransferData{} - if m.transfer != nil { - ret.FungibleTokenPacketData = *m.transfer - } - if m.rollapp != nil { - ret.Rollapp = m.rollapp - } - return ret, nil -} - -func (m *mockRollappKeeper) SetRollapp(_ sdk.Context, rollapp rollapptypes.Rollapp) { - m.rollapp = &rollapp -}