Skip to content

Commit

Permalink
feat(vtransfer): implement +-separated virtual targets
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Oct 17, 2024
1 parent 94aaebc commit de6e865
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 20 deletions.
7 changes: 4 additions & 3 deletions golang/cosmos/x/vtransfer/ibc_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -384,7 +385,7 @@ func (s *IntegrationTestSuite) TestTransferFromAgdToAgd() {
BlockTime: writeAcknowledgementTime,
},
Event: "writeAcknowledgement",
Target: transferData.Receiver,
Target: baseReceiver,
Packet: packet,
Acknowledgement: ack.Acknowledgement(),
},
Expand Down
80 changes: 63 additions & 17 deletions golang/cosmos/x/vtransfer/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
"context"
"encoding/json"
"strings"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit de6e865

Please sign in to comment.