diff --git a/modules/apps/transfer/keeper/hooks.go b/modules/apps/transfer/keeper/hooks.go new file mode 100644 index 00000000000..beb46c7955b --- /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/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 1d505debdb9..67c1fd6ce4f 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -26,6 +26,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 161295d0b70..9c24c9c12f5 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 @@ -162,6 +163,10 @@ func (k Keeper) SendTransfer( return err } + if k.hooks != nil { + k.hooks.AfterSendTransfer(ctx, sourcePort, sourceChannel, token, sender, receiver, isSource) + } + defer func() { telemetry.SetGaugeWithLabels( []string{"tx", "msg", "ibc", "transfer"}, @@ -213,7 +218,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 @@ -257,6 +263,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 } @@ -316,6 +325,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 } @@ -358,7 +370,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 { @@ -369,6 +382,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 } @@ -383,6 +399,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) + } +}