-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(vtransfer): extract base address from parameterized address #10250
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package types | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/cosmos/cosmos-sdk/codec" | ||
|
||
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" | ||
ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" | ||
) | ||
|
||
type AddressRole string | ||
|
||
const ( | ||
RoleSender AddressRole = "Sender" | ||
RoleReceiver AddressRole = "Receiver" | ||
) | ||
|
||
func trimSlashPrefix(s string) string { | ||
return strings.TrimPrefix(s, "/") | ||
} | ||
|
||
// ExtractBaseAddress extracts the base address from a parameterized address. | ||
// It removes all subpath and query components from addr. | ||
func ExtractBaseAddress(addr string) (string, error) { | ||
parsed, err := url.Parse(addr) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// Specify the fields and values we expect. Unspecified fields will only | ||
// match if they are zero values in order to be robust against extensions to | ||
// the url.URL struct. | ||
// | ||
// Remove leading slashes from the path fields so that only parsed relative | ||
// paths match the expected test. | ||
expected := url.URL{ | ||
Path: trimSlashPrefix(parsed.Path), | ||
RawPath: trimSlashPrefix(parsed.RawPath), | ||
RawQuery: parsed.RawQuery, | ||
Fragment: parsed.Fragment, | ||
RawFragment: parsed.RawFragment, | ||
|
||
// Skip over parsing control flags. | ||
ForceQuery: parsed.ForceQuery, | ||
OmitHost: parsed.OmitHost, | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
IMO doing
is still very readable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I'm relying on all of the non-allowlisted fields to be empty, otherwise to fail. That was the most concise and robust way of ensuring the parsing worked correctly. I had originally done an if-ladder and found it to be much less readable than the declaration of However, I'll wait for some other opinions on this, and keep it or change it based on the group's agreement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this approach and in my inherited Go practice, relying on the zero values for a struct is idiomatic. This is robust against evolution of the struct in a way that an if-ladder would not be, since we want the presence of non-zero values in future fields to result in errors. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with @kriskowal. I also rely on Go zero values, I vote for this approach as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @LuqiPan were you pointing out that |
||
|
||
if *parsed != expected { | ||
return "", fmt.Errorf("address must be relative path with optional query and fragment, got %s", addr) | ||
} | ||
|
||
baseAddr, _, _ := strings.Cut(expected.Path, "/") | ||
if baseAddr == "" { | ||
return "", fmt.Errorf("base address cannot be empty") | ||
} | ||
|
||
return baseAddr, nil | ||
} | ||
|
||
// extractBaseTransferData returns the base address from the transferData.Sender | ||
// (if RoleSender) or transferData.Receiver (if RoleReceiver). Errors in | ||
// determining the base address are ignored... we then assume the base address | ||
// is exactly the original address. If newTransferData is not nil, it will be | ||
// populated with a new FungibleTokenPacketData consisting of the role replaced | ||
// with its base address. | ||
func extractBaseTransferData(transferData transfertypes.FungibleTokenPacketData, role AddressRole, newTransferData *transfertypes.FungibleTokenPacketData) (string, error) { | ||
var target string | ||
sender := transferData.Sender | ||
receiver := transferData.Receiver | ||
|
||
switch role { | ||
case RoleSender: | ||
baseSender, err := ExtractBaseAddress(sender) | ||
if err == nil { | ||
sender = baseSender | ||
} | ||
target = sender | ||
|
||
case RoleReceiver: | ||
baseReceiver, err := ExtractBaseAddress(receiver) | ||
if err == nil { | ||
receiver = baseReceiver | ||
} | ||
target = receiver | ||
|
||
default: | ||
err := fmt.Errorf("invalid address role: %s", role) | ||
return target, err | ||
} | ||
|
||
if newTransferData == nil { | ||
return target, nil | ||
} | ||
|
||
// Create the new transfer data. | ||
*newTransferData = transfertypes.NewFungibleTokenPacketData( | ||
transferData.Denom, | ||
transferData.Amount, | ||
sender, receiver, | ||
transferData.Memo, | ||
) | ||
|
||
return target, nil | ||
} | ||
|
||
// ExtractBaseAddressFromPacket returns the base address from a transfer | ||
// packet's data, either Sender (if role is RoleSender) or Receiver (if role is | ||
// RoleReceiver). | ||
// Errors in determining the base address are ignored... we then assume the base | ||
// address is exactly the original address. | ||
// If newPacket is not nil, it is populated with a new transfer packet whose | ||
// corresponding Sender or Receiver is replaced with the extracted base address. | ||
func ExtractBaseAddressFromPacket(cdc codec.Codec, packet ibcexported.PacketI, role AddressRole, newPacket *channeltypes.Packet) (string, error) { | ||
transferData := transfertypes.FungibleTokenPacketData{} | ||
if err := cdc.UnmarshalJSON(packet.GetData(), &transferData); err != nil { | ||
return "", err | ||
} | ||
|
||
var newTransferData *transfertypes.FungibleTokenPacketData | ||
if newPacket != nil { | ||
// Capture the transfer data for the new packet. | ||
newTransferData = &transfertypes.FungibleTokenPacketData{} | ||
} | ||
target, err := extractBaseTransferData(transferData, role, newTransferData) | ||
if err != nil { | ||
return target, err | ||
} | ||
|
||
if newPacket == nil { | ||
return target, nil | ||
} | ||
|
||
// Create a new packet with the new transfer packet data. | ||
// Re-serialize the packet data with the base addresses. | ||
newData, err := cdc.MarshalJSON(newTransferData) | ||
if err != nil { | ||
return target, err | ||
} | ||
|
||
// Create the new packet. | ||
th := packet.GetTimeoutHeight() | ||
*newPacket = channeltypes.NewPacket( | ||
newData, packet.GetSequence(), | ||
packet.GetSourcePort(), packet.GetSourceChannel(), | ||
packet.GetDestPort(), packet.GetDestChannel(), | ||
clienttypes.NewHeight(th.GetRevisionNumber(), th.GetRevisionHeight()), | ||
packet.GetTimeoutTimestamp(), | ||
) | ||
|
||
return target, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is such a good change. 👍