From 531761c95d4ba0d6c66d91be48cb647fa43bd81e Mon Sep 17 00:00:00 2001 From: Dreamer Date: Fri, 8 Mar 2024 16:17:08 +0800 Subject: [PATCH] add axelar IBCMiddleware --- app/keepers/keepers.go | 10 +- modules/axelar/ibc_middleware.go | 174 +++++++++++++++++++++++++++++++ modules/axelar/types/handler.go | 39 +++++++ modules/axelar/types/types.go | 54 ++++++++++ 4 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 modules/axelar/ibc_middleware.go create mode 100644 modules/axelar/types/handler.go create mode 100644 modules/axelar/types/types.go diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index dcdc503fc..a4b76c8d1 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -105,6 +105,7 @@ import ( tokentypes "github.com/irisnet/irismod/modules/token/types" tokenv1 "github.com/irisnet/irismod/modules/token/types/v1" + "github.com/irisnet/irishub/v2/modules/axelar" guardiankeeper "github.com/irisnet/irishub/v2/modules/guardian/keeper" guardiantypes "github.com/irisnet/irishub/v2/modules/guardian/types" "github.com/irisnet/irishub/v2/modules/internft" @@ -395,7 +396,6 @@ func New( appKeepers.scopedTransferKeeper, ) appKeepers.TransferModule = transfer.NewAppModule(appKeepers.IBCTransferKeeper) - transferIBCModule := transfer.NewIBCModule(appKeepers.IBCTransferKeeper) appKeepers.IBCNFTTransferKeeper = ibcnfttransferkeeper.NewKeeper( appCodec, @@ -411,10 +411,16 @@ func New( appKeepers.IBCNftTransferModule = nfttransfer.NewAppModule(appKeepers.IBCNFTTransferKeeper) nfttransferIBCModule := nfttransfer.NewIBCModule(appKeepers.IBCNFTTransferKeeper) + + // create IBC module from bottom to top of stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(appKeepers.IBCTransferKeeper) + transferStack = axelar.NewIBCMiddleware(transferStack,nil) + // routerModule := router.NewAppModule(app.RouterKeeper, transferIBCModule) // create static IBC router, add transfer route, then set and seal it ibcRouter := porttypes.NewRouter(). - AddRoute(ibctransfertypes.ModuleName, transferIBCModule). + AddRoute(ibctransfertypes.ModuleName, transferStack). AddRoute(ibcnfttransfertypes.ModuleName, nfttransferIBCModule). AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) appKeepers.IBCKeeper.SetRouter(ibcRouter) diff --git a/modules/axelar/ibc_middleware.go b/modules/axelar/ibc_middleware.go new file mode 100644 index 000000000..974e5cbb6 --- /dev/null +++ b/modules/axelar/ibc_middleware.go @@ -0,0 +1,174 @@ +package axelar + +import ( + "encoding/json" + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/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/irisnet/irishub/v2/modules/axelar/types" +) + +// IBCMiddleware implements the IBCMiddleware interface +type IBCMiddleware struct { + app porttypes.IBCModule + handler types.GeneralMessageHandler +} + +// NewIBCMiddleware creates a new IBCMiddleware +func NewIBCMiddleware(app porttypes.IBCModule, handler types.GeneralMessageHandler) IBCMiddleware { + if handler == nil { + handler = types.EmptyHandler{} + } + return IBCMiddleware{ + app: app, + handler: handler, + } +} + +// OnChanOpenInit implements the IBCModule interface +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // call underlying callback + return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) +} + +// OnChanOpenTry implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) +} + +// OnChanOpenAck implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +// OnChanOpenConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +// OnChanCloseConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +// OnRecvPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + ack := im.app.OnRecvPacket(ctx, packet, relayer) + if !ack.Success() { + return ack + } + + var data transfertypes.FungibleTokenPacketData + if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return channeltypes.NewErrorAcknowledgement(fmt.Errorf("cannot unmarshal ICS-20 transfer packet data")) + } + + // authenticate the message with packet sender + channel-id + // TODO: authenticate the message with channel-id + if data.Sender != types.AxelarGMPAcc { + return ack + } + + var ( + msg types.Message + err error + ) + + if err = json.Unmarshal([]byte(data.GetMemo()), &msg); err != nil { + return channeltypes.NewErrorAcknowledgement(fmt.Errorf("cannot unmarshal memo")) + } + + switch msg.Type { + case types.TypePureMessage: + err = im.handler.HandleGeneralMessage(ctx, msg.SourceChain, msg.SourceAddress, data.Receiver, msg.Payload) + case types.TypeMessageWithToken: + amt, ok := sdkmath.NewIntFromString(data.Amount) + if !ok { + return channeltypes.NewErrorAcknowledgement(errorsmod.Wrapf(transfertypes.ErrInvalidAmount, "unable to parse transfer amount (%s) into sdk.Int", data.Amount)) + } + + denom := types.ParseDenom(packet, data.Denom) + err = im.handler.HandleGeneralMessageWithToken(ctx, msg.SourceChain, msg.SourceAddress, data.Receiver, msg.Payload, sdk.NewCoin(denom, amt)) + default: + err = fmt.Errorf("unrecognized mesasge type: %d", msg.Type) + } + + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + return ack +} + +// OnAcknowledgementPacket implements the IBCMiddleware interface +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 implements the IBCMiddleware interface +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + return im.app.OnTimeoutPacket(ctx, packet, relayer) +} diff --git a/modules/axelar/types/handler.go b/modules/axelar/types/handler.go new file mode 100644 index 000000000..fa773ed74 --- /dev/null +++ b/modules/axelar/types/handler.go @@ -0,0 +1,39 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GeneralMessageHandler handles messages from axelar +type GeneralMessageHandler interface { + HandleGeneralMessage(ctx sdk.Context, srcChain, srcAddress string, destAddress string, payload []byte) error + HandleGeneralMessageWithToken(ctx sdk.Context, srcChain, srcAddress string, destAddress string, payload []byte, coin sdk.Coin) error +} + +// EmptyHandler implements the GeneralMessageHandler +type EmptyHandler struct{} + +// HandleGeneralMessage implements the GeneralMessageHandler +func (h EmptyHandler) HandleGeneralMessage(ctx sdk.Context, srcChain, srcAddress string, destAddress string, payload []byte) error { + ctx.Logger().Info("HandleGeneralMessage called", + "srcChain", srcChain, + "srcAddress", srcAddress, + "destAddress", destAddress, + "payload", payload, + "module", "axelar", + ) + return nil +} + +// HandleGeneralMessageWithToken implements the GeneralMessageHandler +func (h EmptyHandler) HandleGeneralMessageWithToken(ctx sdk.Context, srcChain, srcAddress string, destAddress string, payload []byte, coin sdk.Coin) error { + ctx.Logger().Info("HandleGeneralMessageWithToken called", + "srcChain", srcChain, + "srcAddress", srcAddress, + "destAddress", destAddress, + "payload", payload, + "coin", coin, + "module", "axelar", + ) + return nil +} diff --git a/modules/axelar/types/types.go b/modules/axelar/types/types.go new file mode 100644 index 000000000..e1942d2f5 --- /dev/null +++ b/modules/axelar/types/types.go @@ -0,0 +1,54 @@ +package types + +import ( + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" +) + +// TODO: AxelarGMPAcc should be moved to config +const AxelarGMPAcc = "axelar1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5" + +// Message is attached in ICS20 packet memo field +type Message struct { + SourceChain string `json:"source_chain"` + SourceAddress string `json:"source_address"` + Payload []byte `json:"payload"` + Type int64 `json:"type"` +} + +// MessageType is the type of message +type MessageType int + +const ( + // TypeUnrecognized means coin type is unrecognized + TypeUnrecognized = iota + // TypePureMessage is a general message + TypePureMessage + // TypeMessageWithToken is a general message with token + TypeMessageWithToken +) + +// ParseDenom convert denom to receiver chain representation +func ParseDenom(packet channeltypes.Packet, denom string) string { + if transfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), denom) { + // remove prefix added by sender chain + voucherPrefix := transfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) + unprefixedDenom := denom[len(voucherPrefix):] + + // coin denomination used in sending from the escrow address + denom = unprefixedDenom + + // The denomination used to send the coins is either the native denom or the hash of the path + // if the denomination is not native. + denomTrace := transfertypes.ParseDenomTrace(unprefixedDenom) + if denomTrace.Path != "" { + denom = denomTrace.IBCDenom() + } + return denom + } + + prefixedDenom := transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + denom + denom = transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + + return denom +} \ No newline at end of file