From 51155f0fc21b33aa19b34d3bbd170374cd1a8d3b Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 24 Feb 2024 22:47:04 +0300 Subject: [PATCH] feat: add multichain tokens support for IBC MsgTransfer --- pkg/data_fetcher/data_fetcher.go | 59 +++++++++++++---- pkg/messages/msg_transfer.go | 66 ++++++++++++++++++-- pkg/messages/packet/fungible_token_packet.go | 2 +- pkg/tendermint/api/api_client.go | 16 +++++ pkg/types/amount/amount.go | 7 +++ pkg/types/data_fetcher.go | 11 ++-- pkg/types/query_info/query_info.go | 1 + pkg/types/responses/ibc_denom_trace.go | 7 +++ 8 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 pkg/types/responses/ibc_denom_trace.go diff --git a/pkg/data_fetcher/data_fetcher.go b/pkg/data_fetcher/data_fetcher.go index 1ba802e..423dc85 100644 --- a/pkg/data_fetcher/data_fetcher.go +++ b/pkg/data_fetcher/data_fetcher.go @@ -6,6 +6,9 @@ import ( "main/pkg/metrics" amountPkg "main/pkg/types/amount" "strconv" + "strings" + + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "main/pkg/alias_manager" "main/pkg/cache" @@ -378,7 +381,7 @@ func (f *DataFetcher) GetIbcRemoteChainID( for _, node := range f.TendermintApiClients[chain.Name] { ibcChannelClientStateResponse, err, queryInfo := node.GetIbcConnectionClientState(ibcChannel.ConnectionHops[0]) - f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcChannel) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcConnectionClientState) if err != nil { f.Logger.Error().Err(err).Msg("Error fetching IBC client state") continue @@ -397,23 +400,57 @@ func (f *DataFetcher) GetIbcRemoteChainID( return ibcClientState.ClientState.ChainId, true } +func (f *DataFetcher) GetDenomTrace( + chain *configTypes.Chain, + denom string, +) (*transferTypes.DenomTrace, bool) { + denomSplit := strings.Split(denom, "/") + if len(denomSplit) != 2 || denomSplit[0] != transferTypes.DenomPrefix { + f.Logger.Error().Msg("Invalid IBC prefix provided") + return nil, false + } + + denomHash := denomSplit[1] + + keyName := chain.Name + "_denom_trace_" + denom + + if cachedEntry, cachedEntryPresent := f.Cache.Get(keyName); cachedEntryPresent { + if cachedEntryParsed, ok := cachedEntry.(*transferTypes.DenomTrace); ok { + return cachedEntryParsed, true + } + + f.Logger.Error().Msg("Could not convert cached staking params to *types.DenomTrace") + return nil, false + } + + for _, node := range f.TendermintApiClients[chain.Name] { + notCachedEntry, err, queryInfo := node.GetIbcDenomTrace(denomHash) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcDenomTrace) + + if err != nil { + f.Logger.Error().Err(err).Msg("Error fetching IBC denom trace") + continue + } + + f.Cache.Set(keyName, notCachedEntry) + return notCachedEntry, true + } + + f.Logger.Error().Msg("Could not connect to any nodes to get IBC denom trace") + return nil, false +} + func (f *DataFetcher) GetAliasManager() *alias_manager.AliasManager { return f.AliasManager } -func (f *DataFetcher) FindMultichainDenom( +func (f *DataFetcher) FindChainById( chainID string, - baseDenom string, -) (*configTypes.Chain, *configTypes.DenomInfo, bool) { +) (*configTypes.Chain, bool) { chain := f.Config.Chains.FindByChainID(chainID) if chain == nil { - return nil, nil, false - } - - denom := chain.Denoms.Find(baseDenom) - if denom == nil { - return nil, nil, false + return nil, false } - return chain, denom, true + return chain, true } diff --git a/pkg/messages/msg_transfer.go b/pkg/messages/msg_transfer.go index 580c453..e01dce4 100644 --- a/pkg/messages/msg_transfer.go +++ b/pkg/messages/msg_transfer.go @@ -18,6 +18,9 @@ type MsgTransfer struct { Sender configTypes.Link Receiver configTypes.Link + SrcChannel string + SrcPort string + Chain *configTypes.Chain } @@ -28,10 +31,12 @@ func ParseMsgTransfer(data []byte, chain *configTypes.Chain, height int64) (type } return &MsgTransfer{ - Token: amount.AmountFrom(parsedMessage.Token), - Sender: chain.GetWalletLink(parsedMessage.Sender), - Receiver: configTypes.Link{Value: parsedMessage.Receiver}, - Chain: chain, + Token: amount.AmountFrom(parsedMessage.Token), + Sender: chain.GetWalletLink(parsedMessage.Sender), + Receiver: configTypes.Link{Value: parsedMessage.Receiver}, + SrcChannel: parsedMessage.SourceChannel, + SrcPort: parsedMessage.SourcePort, + Chain: chain, }, nil } @@ -40,6 +45,8 @@ func (m MsgTransfer) Type() string { } func (m *MsgTransfer) GetAdditionalData(fetcher types.DataFetcher) { + m.FetchRemoteChainData(fetcher) + fetcher.PopulateAmount(m.Chain, m.Token) if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Sender.Value); alias != "" { @@ -47,6 +54,57 @@ func (m *MsgTransfer) GetAdditionalData(fetcher types.DataFetcher) { } } +func (m *MsgTransfer) FetchRemoteChainData(fetcher types.DataFetcher) { + // p.Receiver is always someone from the remote chain, so we need to fetch the data + // from cross-chain. + // p.Sender is on native chain, so we can use p.Chain to generate links + // and denoms and prices. + + // If it's an IBC token (like, withdraw on Osmosis) - we need to figure out what + // the original denom is, to convert it, and also take the remote chain for links + // generation. + // If it's a native token - just take the denom from the current chain, but also fetch + // the remote chain for links generation. + var trace ibcTypes.DenomTrace + if m.Token.IsIbcToken() { + externalTrace, found := fetcher.GetDenomTrace(m.Chain, m.Token.Denom) + if !found { + return + } + trace = *externalTrace + } else { + trace = ibcTypes.ParseDenomTrace(m.Token.Denom) + } + + m.Token.Denom = trace.BaseDenom + m.Token.BaseDenom = trace.BaseDenom + + if trace.IsNativeDenom() { + fetcher.PopulateAmount(m.Chain, m.Token) + } + + originalChainID, fetched := fetcher.GetIbcRemoteChainID(m.Chain, m.SrcChannel, m.SrcPort) + + if !fetched { + return + } + + chain, found := fetcher.FindChainById(originalChainID) + if !found { + return + } + + if !trace.IsNativeDenom() { + fetcher.PopulateAmount(chain, m.Token) + } + + m.Receiver = chain.GetWalletLink(m.Receiver.Value) + + if alias := fetcher.GetAliasManager().Get(chain.Name, m.Receiver.Value); alias != "" { + m.Receiver.Title = alias + } +} + func (m *MsgTransfer) GetValues() event.EventValues { return []event.EventValue{ event.From(cosmosTypes.EventTypeMessage, cosmosTypes.AttributeKeyAction, m.Type()), diff --git a/pkg/messages/packet/fungible_token_packet.go b/pkg/messages/packet/fungible_token_packet.go index 1e0ae2c..9ebf4fe 100644 --- a/pkg/messages/packet/fungible_token_packet.go +++ b/pkg/messages/packet/fungible_token_packet.go @@ -76,7 +76,7 @@ func (p *FungibleTokenPacket) FetchRemoteChainData(fetcher types.DataFetcher) { return } - if chain, _, found := fetcher.FindMultichainDenom(originalChainID, trace.BaseDenom); found { + if chain, found := fetcher.FindChainById(originalChainID); found { fetcher.PopulateAmount(chain, p.Token) p.Sender = chain.GetWalletLink(p.Sender.Value) diff --git a/pkg/tendermint/api/api_client.go b/pkg/tendermint/api/api_client.go index 6b48b16..359cc75 100644 --- a/pkg/tendermint/api/api_client.go +++ b/pkg/tendermint/api/api_client.go @@ -8,6 +8,8 @@ import ( "strconv" "time" + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + configTypes "main/pkg/config/types" "main/pkg/types/responses" @@ -143,6 +145,20 @@ func (c *TendermintApiClient) GetIbcConnectionClientState( return &response.IdentifiedClientState, nil, queryInfo } +func (c *TendermintApiClient) GetIbcDenomTrace( + hash string, +) (*types.DenomTrace, error, query_info.QueryInfo) { + url := fmt.Sprintf("/ibc/apps/transfer/v1/denom_traces/%s", hash) + + var response *responses.IbcDenomTraceResponse + err, queryInfo := c.Get(url, &response) + if err != nil { + return nil, err, queryInfo + } + + return &response.DenomTrace, nil, queryInfo +} + func (c *TendermintApiClient) Get(url string, target interface{}) (error, query_info.QueryInfo) { return c.GetWithHeaders(url, target, map[string]string{}) } diff --git a/pkg/types/amount/amount.go b/pkg/types/amount/amount.go index bf35d0a..c84d9db 100644 --- a/pkg/types/amount/amount.go +++ b/pkg/types/amount/amount.go @@ -7,6 +7,8 @@ import ( "math/big" "strings" + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + cosmosTypes "github.com/cosmos/cosmos-sdk/types" ) @@ -56,6 +58,11 @@ func (a Amount) String() string { return fmt.Sprintf("%d%s", value, a.Denom) } +func (a Amount) IsIbcToken() bool { + denomSplit := strings.Split(a.Denom, "/") + return len(denomSplit) == 2 && denomSplit[0] == transferTypes.DenomPrefix +} + type Amounts []*Amount func (a Amounts) String() string { diff --git a/pkg/types/data_fetcher.go b/pkg/types/data_fetcher.go index 47727b5..097bfd3 100644 --- a/pkg/types/data_fetcher.go +++ b/pkg/types/data_fetcher.go @@ -6,6 +6,8 @@ import ( priceFetchers "main/pkg/price_fetchers" "main/pkg/types/amount" "main/pkg/types/responses" + + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ) // This interface is only here to avoid a cyclic dependency @@ -32,8 +34,9 @@ type DataFetcher interface { GetStakingParams(chain *configTypes.Chain) (*responses.StakingParams, bool) GetAliasManager() *alias_manager.AliasManager GetIbcRemoteChainID(chain *configTypes.Chain, channel, port string) (string, bool) - FindMultichainDenom( - chainID string, - baseDenom string, - ) (*configTypes.Chain, *configTypes.DenomInfo, bool) + FindChainById(chainID string) (*configTypes.Chain, bool) + GetDenomTrace( + chain *configTypes.Chain, + denom string, + ) (*transferTypes.DenomTrace, bool) } diff --git a/pkg/types/query_info/query_info.go b/pkg/types/query_info/query_info.go index fac9cd3..564e76b 100644 --- a/pkg/types/query_info/query_info.go +++ b/pkg/types/query_info/query_info.go @@ -12,6 +12,7 @@ const ( QueryTypeValidator QueryType = "validator" QueryTypeIbcChannel QueryType = "ibc_channel" QueryTypeIbcConnectionClientState QueryType = "ibc_connection_client_state" + QueryTypeIbcDenomTrace QueryType = "ibc_denom_trace" ) func GetQueryTypes() []QueryType { diff --git a/pkg/types/responses/ibc_denom_trace.go b/pkg/types/responses/ibc_denom_trace.go new file mode 100644 index 0000000..e6b0e2b --- /dev/null +++ b/pkg/types/responses/ibc_denom_trace.go @@ -0,0 +1,7 @@ +package responses + +import "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + +type IbcDenomTraceResponse struct { + DenomTrace types.DenomTrace `json:"denom_trace"` +}