diff --git a/modules/apps/transfer/keeper/hooks.go b/modules/apps/transfer/keeper/hooks.go new file mode 100644 index 00000000000..137c7ee34c5 --- /dev/null +++ b/modules/apps/transfer/keeper/hooks.go @@ -0,0 +1,52 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" +) + +var _ types.TransferHooks = Keeper{} + +func (k Keeper) AfterSendTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + isSource bool) { + if k.hooks != nil { + k.hooks.AfterSendTransfer(ctx, sourcePort, sourceChannel, token, sender, receiver, isSource) + } +} + +func (k Keeper) AfterRecvTransfer( + ctx sdk.Context, + destPort, destChannel string, + token sdk.Coin, + receiver string, + isSource bool) { + if k.hooks != nil { + k.hooks.AfterRecvTransfer(ctx, destPort, destChannel, token, receiver, isSource) + } +} + +func (k Keeper) AfterRefundTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender string, + isSource bool) { + if k.hooks != nil { + k.hooks.AfterRefundTransfer(ctx, sourcePort, sourceChannel, token, sender, isSource) + } +} + +func (k *Keeper) SetHooks(sh types.TransferHooks) *Keeper { + if k.hooks != nil { + panic("cannot set hooks twice") + } + + k.hooks = sh + + return k +} diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go index 0c96e2219d2..64e2afc496c 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -27,6 +27,7 @@ type Keeper struct { authKeeper types.AccountKeeper bankKeeper types.BankKeeper scopedKeeper capabilitykeeper.ScopedKeeper + hooks types.TransferHooks } // NewKeeper creates a new IBC transfer Keeper instance diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index ab7f3751588..6117e20794a 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -110,7 +110,8 @@ func (k Keeper) SendTransfer( // chain inside the packet data. The receiving chain will perform denom // prefixing as necessary. - if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { + isSource := types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) + if isSource { labels = append(labels, telemetry.NewLabel(coretypes.LabelSource, "true")) // create the escrow address for the tokens @@ -178,6 +179,9 @@ func (k Keeper) SendTransfer( ) }() + if k.hooks != nil { + k.hooks.AfterSendTransfer(ctx, sourcePort, sourceChannel, token, sender, receiver, isSource) + } return nil } @@ -221,7 +225,8 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t // chain would have prefixed with DestPort and DestChannel when originally // receiving this coin as seen in the "sender chain is the source" condition. - if types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { + isSource := types.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) + if isSource { // sender chain is not the source, unescrow tokens // remove prefix added by sender chain @@ -267,6 +272,9 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t ) }() + if k.hooks != nil { + k.hooks.AfterRecvTransfer(ctx, packet.DestinationPort, packet.DestinationChannel, token, data.Receiver, isSource) + } return nil } @@ -327,6 +335,9 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t ) }() + if k.hooks != nil { + k.hooks.AfterRecvTransfer(ctx, packet.DestinationPort, packet.DestinationChannel, voucher, data.Receiver, isSource) + } return nil } @@ -374,7 +385,8 @@ func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, d return err } - if types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { + isSource := types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) + if isSource { // unescrow tokens back to sender escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) if err := k.bankKeeper.SendCoins(ctx, escrowAddress, sender, sdk.NewCoins(token)); err != nil { @@ -385,6 +397,9 @@ func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, d return sdkerrors.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") } + if k.hooks != nil { + k.hooks.AfterRefundTransfer(ctx, packet.SourcePort, packet.SourceChannel, token, data.Sender, isSource) + } return nil } @@ -399,6 +414,9 @@ func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, d panic(fmt.Sprintf("unable to send coins from module to account despite previously minting coins to module account: %v", err)) } + if k.hooks != nil { + k.hooks.AfterRefundTransfer(ctx, packet.SourcePort, packet.SourceChannel, token, data.Sender, isSource) + } return nil } diff --git a/modules/apps/transfer/types/hooks.go b/modules/apps/transfer/types/hooks.go new file mode 100644 index 00000000000..16c88150404 --- /dev/null +++ b/modules/apps/transfer/types/hooks.go @@ -0,0 +1,67 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type TransferHooks interface { + AfterSendTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + isSource bool) + AfterRecvTransfer( + ctx sdk.Context, + destPort, destChannel string, + token sdk.Coin, + receiver string, + isSource bool) + AfterRefundTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender string, + isSource bool) +} + +type MultiTransferHooks []TransferHooks + +func NewMultiTransferHooks(hooks ...TransferHooks) MultiTransferHooks { + return hooks +} + +func (mths MultiTransferHooks) AfterSendTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + isSource bool) { + for i := range mths { + mths[i].AfterSendTransfer(ctx, sourcePort, sourceChannel, token, sender, receiver, isSource) + } +} + +func (mths MultiTransferHooks) AfterRecvTransfer( + ctx sdk.Context, + destPort, destChannel string, + token sdk.Coin, + receiver string, + isSource bool) { + for i := range mths { + mths[i].AfterRecvTransfer(ctx, destPort, destChannel, token, receiver, isSource) + } +} + +func (mths MultiTransferHooks) AfterRefundTransfer( + ctx sdk.Context, + sourcePort, sourceChannel string, + token sdk.Coin, + sender string, + isSource bool) { + for i := range mths { + mths[i].AfterRefundTransfer(ctx, sourcePort, sourceChannel, token, sender, isSource) + } +}