From de6e86582c2c70899b4da977f6616a8b69a16916 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Wed, 9 Oct 2024 14:38:05 -0600 Subject: [PATCH] feat(vtransfer): implement `+`-separated virtual targets --- .../cosmos/x/vtransfer/ibc_middleware_test.go | 7 +- golang/cosmos/x/vtransfer/keeper/keeper.go | 80 +++++++++++++++---- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/golang/cosmos/x/vtransfer/ibc_middleware_test.go b/golang/cosmos/x/vtransfer/ibc_middleware_test.go index ab82e14e09db..fde4f3afc174 100644 --- a/golang/cosmos/x/vtransfer/ibc_middleware_test.go +++ b/golang/cosmos/x/vtransfer/ibc_middleware_test.go @@ -331,18 +331,19 @@ func (s *IntegrationTestSuite) TestTransferFromAgdToAgd() { s.Run("TransferFromAgdToAgd", func() { // create a transfer packet's data contents + baseReceiver := s.chainB.SenderAccounts[1].SenderAccount.GetAddress().String() transferData := ibctransfertypes.NewFungibleTokenPacketData( "uosmo", "1000000", s.chainA.SenderAccount.GetAddress().String(), - s.chainB.SenderAccounts[1].SenderAccount.GetAddress().String(), + baseReceiver+"+arbitrary-data", `"This is a JSON memo"`, ) // Register the sender and receiver as bridge targets on their specific // chain. s.RegisterBridgeTarget(s.chainA, transferData.Sender) - s.RegisterBridgeTarget(s.chainB, transferData.Receiver) + s.RegisterBridgeTarget(s.chainB, baseReceiver) s.mintToAddress(s.chainA, s.chainA.SenderAccount.GetAddress(), transferData.Denom, transferData.Amount) @@ -384,7 +385,7 @@ func (s *IntegrationTestSuite) TestTransferFromAgdToAgd() { BlockTime: writeAcknowledgementTime, }, Event: "writeAcknowledgement", - Target: transferData.Receiver, + Target: baseReceiver, Packet: packet, Acknowledgement: ack.Acknowledgement(), }, diff --git a/golang/cosmos/x/vtransfer/keeper/keeper.go b/golang/cosmos/x/vtransfer/keeper/keeper.go index 36701a544e7d..73b3b9b2fe9b 100644 --- a/golang/cosmos/x/vtransfer/keeper/keeper.go +++ b/golang/cosmos/x/vtransfer/keeper/keeper.go @@ -3,6 +3,7 @@ package keeper import ( "context" "encoding/json" + "strings" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" @@ -17,6 +18,7 @@ import ( "github.com/Agoric/agoric-sdk/golang/cosmos/x/vibc" vibctypes "github.com/Agoric/agoric-sdk/golang/cosmos/x/vibc/types" transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" host "github.com/cosmos/ibc-go/v6/modules/core/24-host" @@ -34,6 +36,7 @@ var _ vm.PortHandler = (*Keeper)(nil) const ( watchedAddressStoreKeyPrefix = "watchedAddress/" watchedAddressSentinel = "y" + supplementalDataSeparator = "+" ) // Keeper handles the interceptions from the vtransfer IBC middleware, passing @@ -101,12 +104,42 @@ func (k Keeper) GetReceiverImpl() vibctypes.ReceiverImpl { return k } +// Remove the supplemental data from the packet sender (if senderIsLocal) or +// receiver (if !senderIsLocal), since the local ibcModule doesn't understand +// them. +func (k Keeper) packetWithoutSupplementalAddressData(packet channeltypes.Packet, senderIsLocal bool) channeltypes.Packet { + transferData := transfertypes.FungibleTokenPacketData{} + if err := k.cdc.UnmarshalJSON(packet.GetData(), &transferData); err != nil { + return packet + } + if senderIsLocal { + transferData.Sender, _ = parseAddress(transferData.Sender) + } else { + transferData.Receiver, _ = parseAddress(transferData.Receiver) + } + data, _ := k.cdc.MarshalJSON(&transferData) + height := packet.GetTimeoutHeight() + newPacket := channeltypes.NewPacket( + data, + packet.GetSequence(), + packet.GetSourcePort(), + packet.GetSourceChannel(), + packet.GetDestPort(), + packet.GetDestChannel(), + clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()), + packet.GetTimeoutTimestamp(), + ) + return newPacket +} + // InterceptOnRecvPacket runs the ibcModule and eventually acknowledges a packet. // Many error acknowledgments are sent synchronously, but most cases instead return nil // to tell the IBC system that acknowledgment is async (i.e., that WriteAcknowledgement // will be called later, after the VM has dealt with the packet). func (k Keeper) InterceptOnRecvPacket(ctx sdk.Context, ibcModule porttypes.IBCModule, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement { - ack := ibcModule.OnRecvPacket(ctx, packet, relayer) + // Pass every (stripped-receiver) inbound to the wrapped IBC module. + strippedPacket := k.packetWithoutSupplementalAddressData(packet, false) + ack := ibcModule.OnRecvPacket(ctx, strippedPacket, relayer) if ack == nil { // Already declared to be an async ack. @@ -136,11 +169,12 @@ func (k Keeper) InterceptOnAcknowledgementPacket( acknowledgement []byte, relayer sdk.AccAddress, ) error { - // Pass every acknowledgement to the wrapped IBC module. - modErr := ibcModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + // Pass every (stripped-sender) acknowledgement to the wrapped IBC module. + strippedPacket := k.packetWithoutSupplementalAddressData(packet, true) + modErr := ibcModule.OnAcknowledgementPacket(ctx, strippedPacket, acknowledgement, relayer) // If the sender is not a targeted account, we're done. - sender, _, err := k.parseTransfer(ctx, packet) + sender, _, err := k.findTransferTargets(ctx, packet) if err != nil || sender == "" { return modErr } @@ -163,11 +197,12 @@ func (k Keeper) InterceptOnTimeoutPacket( packet channeltypes.Packet, relayer sdk.AccAddress, ) error { - // Pass every timeout to the wrapped IBC module. - modErr := ibcModule.OnTimeoutPacket(ctx, packet, relayer) + // Pass every (stripped-sender) timeout to the wrapped IBC module. + strippedPacket := k.packetWithoutSupplementalAddressData(packet, true) + modErr := ibcModule.OnTimeoutPacket(ctx, strippedPacket, relayer) // If the sender is not a targeted account, we're done. - sender, _, err := k.parseTransfer(ctx, packet) + sender, _, err := k.findTransferTargets(ctx, packet) if err != nil || sender == "" { return modErr } @@ -185,7 +220,7 @@ func (k Keeper) InterceptOnTimeoutPacket( // InterceptWriteAcknowledgement checks to see if the packet's receiver is a // targeted account, and if so, delegates to the VM. func (k Keeper) InterceptWriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error { - _, receiver, err := k.parseTransfer(ctx, packet) + _, receiver, err := k.findTransferTargets(ctx, packet) if err != nil || receiver == "" { // We can't parse, but that means just to ack directly. return k.WriteAcknowledgement(ctx, chanCap, packet, ack) @@ -200,27 +235,38 @@ func (k Keeper) InterceptWriteAcknowledgement(ctx sdk.Context, chanCap *capabili return nil } -// parseTransfer checks if a packet's sender and/or receiver are targeted accounts. -func (k Keeper) parseTransfer(ctx sdk.Context, packet ibcexported.PacketI) (string, string, error) { +// parseAddress extracts the base address and supplemental data. +func parseAddress(addr string) (string, string) { + // The optional supplemental data is separated from the base address by the + // (optional) first occurence of the separator. + base, supplemental, _ := strings.Cut(addr, supplementalDataSeparator) + return base, supplemental +} + +// findTransferTargets checks if a packet's sender and/or receiver correspond to targeted accounts. +func (k Keeper) findTransferTargets(ctx sdk.Context, packet ibcexported.PacketI) (string, string, error) { var transferData transfertypes.FungibleTokenPacketData err := k.cdc.UnmarshalJSON(packet.GetData(), &transferData) if err != nil { return "", "", err } - var sender string - var receiver string + // Extract the base addresses from the transferData. + senderTarget, _ := parseAddress(transferData.Sender) + receiverTarget, _ := parseAddress(transferData.Receiver) prefixStore := prefix.NewStore( ctx.KVStore(k.key), []byte(watchedAddressStoreKeyPrefix), ) - if prefixStore.Has([]byte(transferData.Sender)) { - sender = transferData.Sender + if !prefixStore.Has([]byte(senderTarget)) { + // Not a targeted sender. + senderTarget = "" } - if prefixStore.Has([]byte(transferData.Receiver)) { - receiver = transferData.Receiver + if !prefixStore.Has([]byte(receiverTarget)) { + // Not a targeted receiver. + receiverTarget = "" } - return sender, receiver, nil + return senderTarget, receiverTarget, nil } // GetWatchedAdresses returns the watched addresses from the keeper as a slice