diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d52cf4ad..7b5b71f816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#3227](https://github.com/poanetwork/blockscout/pull/3227) - Distinguishing of bridged tokens - [#3224](https://github.com/poanetwork/blockscout/pull/3224) - Top tokens page ### Fixes diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index 5c52de358c..03c643ff55 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -116,6 +116,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/custom_tooltips_block_details"; @import "components/_erc721_token_image_container"; @import "components/_inventory_token_instance_image_container"; +@import "components/_external_link"; @import "theme/dark-theme"; diff --git a/apps/block_scout_web/assets/css/components/_external_link.scss b/apps/block_scout_web/assets/css/components/_external_link.scss new file mode 100644 index 0000000000..a61c3eaf34 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_external_link.scss @@ -0,0 +1,21 @@ +.external-link-icon { + float: right; + margin-top: -1px; + + & { + &.active, + &:hover, + &:focus { + path { + fill: $header-icon-color-hover; + } + } + } +} + + +.external-token-icon { + path { + fill: $header-icon-color-hover; + } +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index a9a1442166..05ab0f109d 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -260,18 +260,3 @@ $navbar-logo-width: auto !default; transition: none !important; } } - -.external-link-icon { - float: right; - margin-top: -1px; - - & { - &.active, - &:hover, - &:focus { - path { - fill: $header-icon-color-hover; - } - } - } -} diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index b61fb6ebfe..b3e5453277 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -34,7 +34,8 @@ config :block_scout_web, webapp_url: System.get_env("WEBAPP_URL"), api_url: System.get_env("API_URL"), apps_menu: if(System.get_env("APPS_MENU", "false") == "true", do: true, else: false), - external_apps: System.get_env("EXTERNAL_APPS") + external_apps: System.get_env("EXTERNAL_APPS"), + multi_token_bridge_mediator: System.get_env("MULTI_TOKEN_BRIDGE_MEDIATOR") config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex index 10bc8bd487..00fdcb8c1a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_tile.html.eex @@ -20,6 +20,15 @@ use_custom_tooltip: false %> + + <%= case @token.bridged do %> + <% nil -> %> + + <% true -> %> + + <% false -> %> + <% end %> + <%= if decimals?(@token) do %> <%= format_according_to_decimals(@token.total_supply, @token.decimals) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex index 680f177f54..03f121c89d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex @@ -18,6 +18,9 @@
Address
+ + +
Bridged?
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex index 14e69303b2..a543d4f6e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex @@ -42,6 +42,16 @@ + + <%= if @token.bridged do %> +
+ + Token is bridged + + target="_blank">View Original Token <%= render BlockScoutWeb.IconsView, "_external_link.html" %> + +
+ <% end %>

<%= Address.checksum(@token.contract_address_hash) %>

diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index eb82b2cc6f..59b8a71e40 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -56,4 +56,48 @@ defmodule BlockScoutWeb.Tokens.OverviewView do price = token.usd_value Decimal.mult(tokens, price) end + + def foreign_bridged_token_explorer_link(token) do + chain_id = Map.get(token, :foreign_chain_id) + + base_token_explorer_link = get_base_token_explorer_link(chain_id) + + foreign_token_contract_address_hash_string_no_prefix = + token.foreign_token_contract_address_hash.bytes + |> Base.encode16(case: :lower) + + foreign_token_contract_address_hash_string = "0x" <> foreign_token_contract_address_hash_string_no_prefix + + base_token_explorer_link <> foreign_token_contract_address_hash_string + end + + defp get_base_token_explorer_link(chain_id) when not is_nil(chain_id) do + case Decimal.to_integer(chain_id) do + 100 -> + "https://blockscout.com/poa/xdai/tokens/" + + 99 -> + "https://blockscout.com/poa/core/tokens/" + + 77 -> + "https://blockscout.com/poa/sokol/tokens/" + + 42 -> + "https://kovan.etherscan.io/token/" + + 3 -> + "https://ropsten.etherscan.io/token/" + + 4 -> + "https://rinkeby.etherscan.io/token/" + + 5 -> + "https://goerli.etherscan.io/token/" + + 1 -> + "https://etherscan.io/token/" + end + end + + defp get_base_token_explorer_link(_), do: "https://etherscan.io/" end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 12ef88e1a0..342ad92dab 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -107,7 +107,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 msgid "APIs" msgstr "" @@ -631,7 +631,7 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:99 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:107 msgid "Eth RPC" msgstr "" @@ -732,7 +732,7 @@ msgid "Github" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 msgid "GraphQL" msgstr "" @@ -1175,7 +1175,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:51 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:94 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "QR Code" msgstr "" @@ -1185,7 +1185,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:94 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 msgid "RPC" msgstr "" @@ -1243,14 +1243,14 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:164 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:181 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:172 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:189 msgid "Search" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:162 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:166 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:170 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1446,7 +1446,7 @@ msgid "Topics" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 msgid "Total Supply" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:65 msgid "View Contract" msgstr "" @@ -1715,8 +1715,8 @@ msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:142 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:95 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:103 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:116 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:124 msgid "Close" msgstr "" @@ -1733,7 +1733,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:69 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73 msgid "Decimals" msgstr "" @@ -1850,6 +1850,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:83 +#: lib/block_scout_web/templates/tokens/index.html.eex:4 #: lib/block_scout_web/views/address_view.ex:341 msgid "Tokens" msgstr "" @@ -1930,7 +1932,7 @@ msgid "Write Proxy" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:112 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 msgid "Apps" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 12ef88e1a0..342ad92dab 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -107,7 +107,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 msgid "APIs" msgstr "" @@ -631,7 +631,7 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:99 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:107 msgid "Eth RPC" msgstr "" @@ -732,7 +732,7 @@ msgid "Github" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 msgid "GraphQL" msgstr "" @@ -1175,7 +1175,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:51 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:94 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "QR Code" msgstr "" @@ -1185,7 +1185,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:94 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 msgid "RPC" msgstr "" @@ -1243,14 +1243,14 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:164 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:181 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:172 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:189 msgid "Search" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:162 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:166 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:170 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1446,7 +1446,7 @@ msgid "Topics" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 msgid "Total Supply" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:65 msgid "View Contract" msgstr "" @@ -1715,8 +1715,8 @@ msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:142 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:95 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:103 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:116 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:124 msgid "Close" msgstr "" @@ -1733,7 +1733,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:69 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73 msgid "Decimals" msgstr "" @@ -1850,6 +1850,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:83 +#: lib/block_scout_web/templates/tokens/index.html.eex:4 #: lib/block_scout_web/views/address_view.ex:341 msgid "Tokens" msgstr "" @@ -1930,7 +1932,7 @@ msgid "Write Proxy" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:112 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 msgid "Apps" msgstr "" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index ed0e6d614f..0f1cec0cd4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -70,7 +70,7 @@ defmodule EthereumJSONRPC.Contract do Enum.map(requests, fn _ -> format_error(error) end) end - defp eth_call_request(data, contract_address, id, block_number, from) do + def eth_call_request(data, contract_address, id, block_number, from) do block = case block_number do nil -> "latest" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5ba04bea56..53db30525d 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -22,9 +22,9 @@ defmodule Explorer.Chain do select: 3 ] - import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2] + import EthereumJSONRPC, only: [integer_to_quantity: 1, json_rpc: 2, fetch_block_internal_transactions: 2] - alias ABI.TypeDecoder + alias ABI.{TypeDecoder, TypeEncoder} alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi} @@ -42,6 +42,7 @@ defmodule Explorer.Chain do Address.CurrentTokenBalance, Address.TokenBalance, Block, + BridgedToken, Data, DecompiledSmartContract, Hash, @@ -1728,7 +1729,7 @@ defmodule Explorer.Chain do base_query = from(t in Token, where: t.total_supply > ^0, - order_by: [desc: t.holder_count], + order_by: [desc: t.holder_count, asc: t.name], preload: [:contract_address] ) @@ -3375,6 +3376,151 @@ defmodule Explorer.Chain do Repo.stream_reduce(query, [], &[&1 | &2]) end + @doc """ + Returns a list of token addresses `t:Address.t/0`s that don't have an + bridged property revealed. + """ + def unprocessed_token_addresses_to_reveal_bridged_tokens do + query = + from(t in Token, + where: is_nil(t.bridged), + select: t.contract_address_hash + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end + + @doc """ + Fetches bridges status for tokens. + """ + def fetch_tokens_bridged_status(token_addresses) do + Enum.each(token_addresses, fn token_address_hash -> + created_from_factory_query = + from( + it in InternalTransaction, + where: it.created_contract_address_hash == ^token_address_hash + ) + + created_from_factory = + created_from_factory_query + |> Repo.one() + + if created_from_factory do + multi_token_bridge_mediator = Application.get_env(:block_scout_web, :multi_token_bridge_mediator) + %{transaction_hash: transaction_hash} = created_from_factory + + if multi_token_bridge_mediator && multi_token_bridge_mediator !== "" do + {:ok, multi_token_bridge_mediator_hash} = Chain.string_to_address_hash(multi_token_bridge_mediator) + + created_by_amb_mediator_query = + from( + it in InternalTransaction, + where: it.transaction_hash == ^transaction_hash, + where: it.to_address_hash == ^multi_token_bridge_mediator_hash + ) + + created_by_amb_mediator = + created_by_amb_mediator_query + |> Repo.all() + + if Enum.count(created_by_amb_mediator) > 0 do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + # keccak 256 from getTokenInterfacesVersion() + get_token_interfaces_version_signature = "0x859ba28c" + # keccak 256 from foreignTokenAddress(address) + foreign_token_address_signature = "0x47ac7d6a" + # keccak 256 from bridgeContract() + bridge_contract_signature = "0xcd596583" + # keccak 256 from destinationChainId() + destination_chain_id_signature = "0xb0750611" + + token_address_hash_abi_encoded = + [token_address_hash.bytes] + |> TypeEncoder.encode([:address]) + |> Base.encode16() + + foreign_token_address_method = foreign_token_address_signature <> token_address_hash_abi_encoded + + with {:ok, _} <- + get_token_interfaces_version_signature + |> Contract.eth_call_request(token_address_hash, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments), + {:ok, foreign_token_address_abi_encoded} <- + foreign_token_address_method + |> Contract.eth_call_request( + multi_token_bridge_mediator, + 1, + nil, + nil + ) + |> json_rpc(json_rpc_named_arguments), + {:ok, bridge_contract} <- + bridge_contract_signature + |> Contract.eth_call_request(multi_token_bridge_mediator_hash, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments) do + "0x" <> foreign_token_address_no_prefix = foreign_token_address_abi_encoded + + <<_prefix::binary-size(24), foreign_token_address_hash_string_raw::binary()>> = + foreign_token_address_no_prefix + + foreign_token_address_hash_string = "0x" <> foreign_token_address_hash_string_raw + + {:ok, foreign_token_address_hash} = Chain.string_to_address_hash(foreign_token_address_hash_string) + + "0x" <> bridge_contract_no_prefix = bridge_contract + <<_prefix::binary-size(24), multi_token_bridge_hash_string_raw::binary()>> = bridge_contract_no_prefix + + multi_token_bridge_hash_string = "0x" <> multi_token_bridge_hash_string_raw + + {:ok, foreign_chain_id_abi_encoded} = + destination_chain_id_signature + |> Contract.eth_call_request(multi_token_bridge_hash_string, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments) + + "0x" <> foreign_chain_id_abi_encoded_no_prefix = foreign_chain_id_abi_encoded + {foreign_chain_id, _} = Integer.parse(foreign_chain_id_abi_encoded_no_prefix) + + set_bridged_token_metadata(token_address_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash + }) + + set_token_bridged_status(token_address_hash, true) + end + else + set_token_bridged_status(token_address_hash, false) + end + end + else + set_token_bridged_status(token_address_hash, false) + end + end) + + :ok + end + + defp set_token_bridged_status(token_address_hash, status) do + target_token = Repo.get!(Token, token_address_hash) + token = Changeset.change(target_token, bridged: status) + + Repo.update(token) + end + + defp set_bridged_token_metadata(token_address_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash + }) do + {:ok, _} = + Repo.insert( + %BridgedToken{ + home_token_contract_address_hash: token_address_hash, + foreign_chain_id: foreign_chain_id, + foreign_token_contract_address_hash: foreign_token_address_hash + }, + on_conflict: :nothing + ) + end + @doc """ Fetches a `t:Token.t/0` by an address hash. @@ -3394,8 +3540,11 @@ defmodule Explorer.Chain do query = from( - token in Token, - where: token.contract_address_hash == ^hash + t in Token, + left_join: bt in BridgedToken, + on: t.contract_address_hash == bt.home_token_contract_address_hash, + where: t.contract_address_hash == ^hash, + select: [t, bt] ) query @@ -3405,8 +3554,16 @@ defmodule Explorer.Chain do nil -> {:error, :not_found} - %Token{} = token -> - {:ok, token} + [%Token{} = token, %BridgedToken{} = bridged_token] -> + foreign_token_contract_address_hash = Map.get(bridged_token, :foreign_token_contract_address_hash) + foreign_chain_id = Map.get(bridged_token, :foreign_chain_id) + + extended_token = + token + |> Map.put(:foreign_token_contract_address_hash, foreign_token_contract_address_hash) + |> Map.put(:foreign_chain_id, foreign_chain_id) + + {:ok, extended_token} end end diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex new file mode 100644 index 0000000000..15d13f4018 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -0,0 +1,62 @@ +defmodule Explorer.Chain.BridgedToken do + @moduledoc """ + Represents a bridged token. + + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.Chain.{BridgedToken, Hash, Token} + + @typedoc """ + * `foreign_chain_id` - chain ID of a foreign token + * `foreign_token_contract_address_hash` - Foreign token's contract hash + * `home_token_contract_address` - The `t:Address.t/0` of the home token's contract + * `home_token_contract_address_hash` - Home token's contract hash foreign key + """ + @type t :: %BridgedToken{ + foreign_chain_id: Decimal.t(), + foreign_token_contract_address_hash: Hash.Address.t(), + home_token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), + home_token_contract_address_hash: Hash.Address.t() + } + + @derive {Poison.Encoder, + except: [ + :__meta__, + :home_token_contract_address, + :inserted_at, + :updated_at + ]} + + @primary_key false + schema "bridged_tokens" do + field(:foreign_chain_id, :decimal) + field(:foreign_token_contract_address_hash, Hash.Address) + + belongs_to( + :home_token_contract_address, + Token, + foreign_key: :home_token_contract_address_hash, + primary_key: true, + references: :contract_address_hash, + type: Hash.Address + ) + + timestamps() + end + + @required_attrs ~w(home_token_contract_address_hash)a + @optional_attrs ~w(foreign_chain_id foreign_token_contract_address_hash)a + + @doc false + def changeset(%BridgedToken{} = bridged_token, params \\ %{}) do + bridged_token + |> cast(params, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:home_token_contract_address) + |> unique_constraint(:home_token_contract_address_hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index 551563e4ad..b42b7e7124 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -45,7 +45,8 @@ defmodule Explorer.Chain.Token do cataloged: boolean(), contract_address: %Ecto.Association.NotLoaded{} | Address.t(), contract_address_hash: Hash.Address.t(), - holder_count: non_neg_integer() | nil + holder_count: non_neg_integer() | nil, + bridged: boolean() } @derive {Poison.Encoder, @@ -65,6 +66,7 @@ defmodule Explorer.Chain.Token do field(:type, :string) field(:cataloged, :boolean) field(:holder_count, :integer) + field(:bridged, :boolean) belongs_to( :contract_address, diff --git a/apps/explorer/priv/repo/migrations/20200806125649_token_add_bridged_column.exs b/apps/explorer/priv/repo/migrations/20200806125649_token_add_bridged_column.exs new file mode 100644 index 0000000000..ee059af16c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20200806125649_token_add_bridged_column.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.TokenAddBridgedColumn do + use Ecto.Migration + + def change do + alter table(:tokens) do + add(:bridged, :boolean, null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20200807064700_bridged_tokens_table.exs b/apps/explorer/priv/repo/migrations/20200807064700_bridged_tokens_table.exs new file mode 100644 index 0000000000..a601379fcd --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20200807064700_bridged_tokens_table.exs @@ -0,0 +1,20 @@ +defmodule Explorer.Repo.Migrations.BridgedTokensTable do + use Ecto.Migration + + def change do + create table(:bridged_tokens, primary_key: false) do + add(:foreign_chain_id, :numeric, null: false) + add(:foreign_token_contract_address_hash, :bytea, null: false) + + add( + :home_token_contract_address_hash, + references(:tokens, column: :contract_address_hash, on_delete: :delete_all, type: :bytea), + null: false + ) + + timestamps() + end + + create(unique_index(:bridged_tokens, :home_token_contract_address_hash)) + end +end diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 24b9edd909..f24a08a0d2 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -44,6 +44,8 @@ config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, # config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true # config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true +# System.get_env("MULTI_TOKEN_BRIDGE_MEDIATOR", "") == "" +# config :indexer, Indexer.SetBridgedStatusForTokens.Supervisor, disabled?: true config :indexer, Indexer.Supervisor, enabled: System.get_env("DISABLE_INDEXER") != "true" diff --git a/apps/indexer/lib/indexer/set_bridged_status_for_tokens.ex b/apps/indexer/lib/indexer/set_bridged_status_for_tokens.ex new file mode 100644 index 0000000000..1b62dff4fe --- /dev/null +++ b/apps/indexer/lib/indexer/set_bridged_status_for_tokens.ex @@ -0,0 +1,62 @@ +defmodule Indexer.SetBridgedStatusForTokens do + @moduledoc """ + Peiodically checks unprocessed tokens and sets bridged status. + """ + + use GenServer + + require Logger + + alias Explorer.Chain + + @interval :timer.minutes(1) + + # def child_spec([init_arguments]) do + # child_spec([init_arguments, []]) + # end + + # def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do + # default = %{ + # id: __MODULE__, + # start: {__MODULE__, :start_link, start_link_arguments} + # } + + # Supervisor.child_spec(default, []) + # end + + def start_link([init_opts, gen_server_opts]) do + start_link(init_opts, gen_server_opts) + end + + def start_link(init_opts, gen_server_opts) do + GenServer.start_link(__MODULE__, init_opts, gen_server_opts) + end + + @impl GenServer + def init(opts) do + interval = opts[:interval] || @interval + + Process.send_after(self(), :reveal_unprocessed_tokens, interval) + + {:ok, %{interval: interval}} + end + + @impl GenServer + def handle_info(:reveal_unprocessed_tokens, %{interval: interval} = state) do + Logger.debug(fn -> "Reveal unprocessed tokens" end) + + {:ok, token_addresses} = Chain.unprocessed_token_addresses_to_reveal_bridged_tokens() + + fetch_tokens_bridged_status(token_addresses) + + Process.send_after(self(), :reveal_unprocessed_tokens, interval) + + {:noreply, state} + end + + defp fetch_tokens_bridged_status(token_addresses) do + :ok = Chain.fetch_tokens_bridged_status(token_addresses) + + Logger.debug(fn -> "Bridged status fetched for tokens" end) + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index f22b6de487..590771e149 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -5,7 +5,7 @@ defmodule Indexer.Supervisor do use Supervisor - alias Indexer.{Block, PendingOpsCleaner} + alias Indexer.{Block, PendingOpsCleaner, SetBridgedStatusForTokens} alias Indexer.Block.{Catchup, Realtime} alias Indexer.Fetcher.{ @@ -123,6 +123,7 @@ defmodule Indexer.Supervisor do # Out-of-band fetchers {CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]}, + {SetBridgedStatusForTokens, [[], []]}, # Temporary workers {UncatalogedTokenTransfers.Supervisor, [[]]},