mukan-ibc/modules/apps/transfer/v2/ibc_module.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

200 lines
8.4 KiB
Go

package v2
import (
"bytes"
"fmt"
"strings"
errorsmod "cosmossdk.io/errors"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/internal/events"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/internal/telemetry"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/keeper"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
channeltypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/v2/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/core/api"
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
)
var _ api.IBCModule = (*IBCModule)(nil)
// NewIBCModule creates a new IBCModule given the keeper
func NewIBCModule(k keeper.Keeper) *IBCModule {
return &IBCModule{
keeper: k,
}
}
type IBCModule struct {
keeper keeper.Keeper
}
func (im *IBCModule) OnSendPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, signer sdk.AccAddress) error {
// Enforce that the source and destination portIDs are the same and equal to the transfer portID
// Enforce that the source and destination clientIDs are also in the clientID format that transfer expects: {clientid}-{sequence}
// This is necessary for IBC v2 since the portIDs (and thus the application-application connection) is not prenegotiated
// by the channel handshake
// This restriction can be removed in a future where the trace hop on receive commits to **both** the source and destination portIDs
// rather than just the destination port
if payload.SourcePort != types.PortID || payload.DestinationPort != types.PortID {
return errorsmod.Wrapf(channeltypesv2.ErrInvalidPacket, "payload port ID is invalid: expected %s, got sourcePort: %s destPort: %s", types.PortID, payload.SourcePort, payload.DestinationPort)
}
if !clienttypes.IsValidClientID(sourceChannel) || !clienttypes.IsValidClientID(destinationChannel) {
return errorsmod.Wrapf(channeltypesv2.ErrInvalidPacket, "client IDs must be in valid format: {string}-{number}")
}
data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding)
if err != nil {
return err
}
addressCodec := im.keeper.GetAddressCodec()
sender, err := addressCodec.StringToBytes(data.Sender)
if err != nil {
return err
}
if !bytes.Equal(sender, signer) {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "sender %s is different from signer %s", sender, signer)
}
// Enforce that the base denom does not contain any slashes
// Since IBC v2 packets will no longer have channel identifiers, we cannot rely
// on the channel format to easily divide the trace from the base denomination in ICS20 v1 packets
// The simplest way to prevent any potential issues from arising is to simply disallow any slashes in the base denomination
// This prevents such denominations from being sent with IBCV v2 packets, however we can still support them in IBC v1 packets
// If we enforce that IBC v2 packets are sent with ICS20 v2 and above versions that separate the trace from the base denomination
// in the packet data, then we can remove this restriction.
if strings.Contains(data.Token.Denom.Base, "/") {
return errorsmod.Wrapf(types.ErrInvalidDenomForTransfer, "base denomination %s cannot contain slashes for IBC v2 packet", data.Token.Denom.Base)
}
if err := im.keeper.SendTransfer(ctx, payload.SourcePort, sourceChannel, data.Token, signer); err != nil {
return err
}
events.EmitTransferEvent(ctx, data.Sender, data.Receiver, data.Token, data.Memo)
telemetry.ReportTransfer(payload.SourcePort, sourceChannel, payload.DestinationPort, destinationChannel, data.Token)
return nil
}
func (im *IBCModule) OnRecvPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult {
// Enforce that the source and destination portIDs are the same and equal to the transfer portID
// Enforce that the source and destination clientIDs are also in the clientID format that transfer expects: {clientid}-{sequence}
// This is necessary for IBC v2 since the portIDs (and thus the application-application connection) is not prenegotiated
// by the channel handshake
// This restriction can be removed in a future where the trace hop on receive commits to **both** the source and destination portIDs
// rather than just the destination port
if payload.SourcePort != types.PortID || payload.DestinationPort != types.PortID {
return channeltypesv2.RecvPacketResult{
Status: channeltypesv2.PacketStatus_Failure,
}
}
if !clienttypes.IsValidClientID(sourceChannel) || !clienttypes.IsValidClientID(destinationChannel) {
return channeltypesv2.RecvPacketResult{
Status: channeltypesv2.PacketStatus_Failure,
}
}
var (
ackErr error
data types.InternalTransferRepresentation
)
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
recvResult := channeltypesv2.RecvPacketResult{
Status: channeltypesv2.PacketStatus_Success,
Acknowledgement: ack.Acknowledgement(),
}
// we are explicitly wrapping this emit event call in an anonymous function so that
// the packet data is evaluated after it has been assigned a value.
defer func() {
events.EmitOnRecvPacketEvent(ctx, data, ack, ackErr)
}()
data, ackErr = types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding)
if ackErr != nil {
im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), sequence))
return channeltypesv2.RecvPacketResult{
Status: channeltypesv2.PacketStatus_Failure,
}
}
if ackErr = im.keeper.OnRecvPacket(
ctx,
data,
payload.SourcePort,
sourceChannel,
payload.DestinationPort,
destinationChannel,
); ackErr != nil {
im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), sequence))
return channeltypesv2.RecvPacketResult{
Status: channeltypesv2.PacketStatus_Failure,
}
}
im.keeper.Logger(ctx).Info("successfully handled ICS-20 packet", "sequence", sequence)
telemetry.ReportOnRecvPacket(payload.SourcePort, sourceChannel, payload.DestinationPort, destinationChannel, data.Token)
// NOTE: acknowledgement will be written synchronously during IBC handler execution.
return recvResult
}
func (im *IBCModule) OnTimeoutPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error {
data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding)
if err != nil {
return err
}
// refund tokens
if err := im.keeper.OnTimeoutPacket(ctx, payload.SourcePort, sourceChannel, data); err != nil {
return err
}
events.EmitOnTimeoutEvent(ctx, data)
return nil
}
func (im *IBCModule) OnAcknowledgementPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error {
var ack channeltypes.Acknowledgement
// construct an error acknowledgement if the acknowledgement bytes are the sentinel error acknowledgement so we can use the shared transfer logic
if bytes.Equal(acknowledgement, channeltypesv2.ErrorAcknowledgement[:]) {
// the specific error does not matter
ack = channeltypes.NewErrorAcknowledgement(types.ErrReceiveFailed)
} else {
if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
return errorsmod.Wrapf(ibcerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}
if !ack.Success() {
return errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot pass in a custom error acknowledgement with IBC v2")
}
}
data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding)
if err != nil {
return err
}
if err := im.keeper.OnAcknowledgementPacket(ctx, payload.SourcePort, sourceChannel, data, ack); err != nil {
return err
}
events.EmitOnAcknowledgementPacketEvent(ctx, data, ack)
return nil
}
// UnmarshalPacketData unmarshals the ICS20 packet data based on the version and encoding
// it implements the PacketDataUnmarshaler interface
func (*IBCModule) UnmarshalPacketData(payload channeltypesv2.Payload) (any, error) {
return types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding)
}