diff --git a/x/axelarnet/module.go b/x/axelarnet/module.go index 4eaf299e6..1860a5dc8 100644 --- a/x/axelarnet/module.go +++ b/x/axelarnet/module.go @@ -262,7 +262,7 @@ func (m AxelarnetIBCModule) OnAcknowledgementPacket( return err } - return m.setRoutedPacketFailed(ctx, packet) + return m.setRoutedPacketFailed(ctx, packet, m.bank) } } @@ -287,7 +287,7 @@ func (m AxelarnetIBCModule) OnTimeoutPacket( return err } - return m.setRoutedPacketFailed(ctx, packet) + return m.setRoutedPacketFailed(ctx, packet, m.bank) } // returns the transfer id and delete the existing mapping @@ -334,7 +334,7 @@ func setRoutedPacketCompleted(ctx sdk.Context, k keeper.Keeper, n types.Nexus, p return nil } -func (m AxelarnetIBCModule) setRoutedPacketFailed(ctx sdk.Context, packet channeltypes.Packet) error { +func (m AxelarnetIBCModule) setRoutedPacketFailed(ctx sdk.Context, packet channeltypes.Packet, bank types.BankKeeper) error { // IBC ack/timeout packets, by convention, use the source port/channel to represent native chain -> counterparty chain channel id // https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#definitions port, channel, sequence := packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence() @@ -347,6 +347,11 @@ func (m AxelarnetIBCModule) setRoutedPacketFailed(ctx sdk.Context, packet channe return err } + err = refundFromAssetEscrowAddressToIBCAccount(ctx, packet, bank) + if err != nil { + return err + } + err = lockableAsset.LockFrom(ctx, types.AxelarIBCAccount) if err != nil { return err @@ -395,3 +400,24 @@ func extractTokenFromAckOrTimeoutPacket(packet channeltypes.Packet) sdk.Coin { return sdk.NewCoin(trace.IBCDenom(), amount) } + +// Temporary logic to handle in-transit IBC transfers during upgrade. Previously IBC transfers were sent from the asset +// escrow address, but now they're sent from Axelar IBC account. IBC refunds the token to the original sender, so we move +// the tokens from the asset escrow to the Axelar IBC account for correct processing. +// +// Deprecated: Remove this function after the v1.1 upgrade and ensure no in-transit IBC transfers are left. +func refundFromAssetEscrowAddressToIBCAccount(ctx sdk.Context, packet channeltypes.Packet, bank types.BankKeeper) error { + // Packet is validated by the IBC module, so we can safely assume it's a valid ICS20 packet + data := funcs.Must(types.ToICS20Packet(packet)) + + originalSender := funcs.Must(sdk.AccAddressFromBech32(data.Sender)) + if originalSender.Equals(types.AxelarIBCAccount) { + return nil + } + + denom := ibctransfertypes.ParseDenomTrace(data.Denom).IBCDenom() + transferAmount := funcs.MustOk(sdk.NewIntFromString(data.Amount)) + + token := sdk.NewCoin(denom, transferAmount) + return bank.SendCoins(ctx, originalSender, types.AxelarIBCAccount, sdk.NewCoins(token)) +} diff --git a/x/axelarnet/module_test.go b/x/axelarnet/module_test.go index 280d66bfe..a81479795 100644 --- a/x/axelarnet/module_test.go +++ b/x/axelarnet/module_test.go @@ -197,6 +197,7 @@ func TestIBCModule(t *testing.T) { transfer := funcs.MustOk(k.GetTransfer(ctx, transfer.ID)) assert.Equal(t, types.TransferFailed, transfer.Status) assert.Len(t, lockableAsset.LockFromCalls(), 1) + assert.Len(t, bankK.SendCoinsCalls(), 2) }), whenPendingTransfersExist. @@ -237,5 +238,20 @@ func TestIBCModule(t *testing.T) { assert.Equal(t, nexus.Failed, message.Status) assert.Len(t, lockableAsset.LockFromCalls(), 1) }), + + seqMapsToID. + When2(whenChainIsActivated). + When("lock coin succeeds", lockCoin(true)). + When("packet sender is from IBC account", func() { + fungibleTokenPacket.Sender = types.AxelarIBCAccount.String() + packet = channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), packetSeq, ibctransfertypes.PortID, channelID, ibctransfertypes.PortID, channelID, clienttypes.NewHeight(0, 110), 0) + }). + When2(whenOnTimeout). + Then("should not trigger refund from asset escrow to IBC account", func(t *testing.T) { + transfer := funcs.MustOk(k.GetTransfer(ctx, transfer.ID)) + assert.Equal(t, types.TransferFailed, transfer.Status) + assert.Len(t, lockableAsset.LockFromCalls(), 1) + assert.Len(t, bankK.SendCoinsCalls(), 1) + }), ).Run(t) }