Skip to content

Commit

Permalink
feat(denommetadata): register IBC denom on transfer (#956)
Browse files Browse the repository at this point in the history
  • Loading branch information
zale144 authored Jun 25, 2024
1 parent 2528e5d commit 5ba056c
Show file tree
Hide file tree
Showing 25 changed files with 1,023 additions and 817 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -644,7 +642,6 @@ func New(
app.IBCKeeper.ChannelKeeper,
app.IBCKeeper.ChannelKeeper,
&app.EIBCKeeper,
app.BankKeeper,
)

app.EIBCKeeper.SetDelayedAckKeeper(app.DelayedAckKeeper)
Expand Down Expand Up @@ -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)

Expand Down
9 changes: 8 additions & 1 deletion testutil/keeper/delayedack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
34 changes: 19 additions & 15 deletions x/delayedack/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -42,7 +47,6 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ps paramtype
rollappKeeper: rollappKeeper,
ICS4Wrapper: ics4Wrapper,
channelKeeper: channelKeeper,
bankKeeper: bankKeeper,
EIBCKeeper: eibcKeeper,
}
}
Expand Down
9 changes: 1 addition & 8 deletions x/delayedack/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
}
238 changes: 238 additions & 0 deletions x/denommetadata/ibc_middleware.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 5ba056c

Please sign in to comment.