diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9e3d0d28..882809f84d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ### Features - [#3279](https://github.com/poanetwork/blockscout/pull/3279) - NFT instance: link to the app +- [#3278](https://github.com/poanetwork/blockscout/pull/3278) - Support of fetching of NFT tokens metadata from IPFS - [#3273](https://github.com/poanetwork/blockscout/pull/3273) - Update token metadata at burn/mint events - [#3268](https://github.com/poanetwork/blockscout/pull/3268) - Token total supply on-demand fetcher - [#3261](https://github.com/poanetwork/blockscout/pull/3261) - Bridged tokens table ### Fixes +- [#3276](https://github.com/poanetwork/blockscout/pull/3276) - Bridged tokens status/metadata fetcher refactoring - [#3264](https://github.com/poanetwork/blockscout/pull/3264) - Fix encoding of address output if function input exists - [#3259](https://github.com/poanetwork/blockscout/pull/3259), [#3269](https://github.com/poanetwork/blockscout/pull/3269) - Contract interaction: array input type parsing fix - [#3257](https://github.com/poanetwork/blockscout/pull/3257) - Contracts read/write: method_id instead function_name as a key diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex index 70a7d01400..7b9aa98a61 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.AddressTransactionView do use BlockScoutWeb, :view - alias Explorer.Chain.{Address} + alias Explorer.Chain.Address def format_current_filter(filter) do case filter do diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex index 0cc90aad69..651dee474a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.Helpers do Helper functions for interacting with `t:BlockScoutWeb.Chain.Token` attributes. """ - alias BlockScoutWeb.{CurrencyHelpers} + alias BlockScoutWeb.CurrencyHelpers alias Explorer.Chain.{Address, Token} @doc """ diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex index 81beb2a149..03d7e74cd8 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.Tokens.HolderView do use BlockScoutWeb, :view alias BlockScoutWeb.Tokens.OverviewView - alias Explorer.Chain.{Token} + alias Explorer.Chain.Token @doc """ Checks if the total supply percentage must be shown. diff --git a/apps/explorer/lib/explorer/accounts/accounts.ex b/apps/explorer/lib/explorer/accounts/accounts.ex index dac28ccd4e..b399d37816 100644 --- a/apps/explorer/lib/explorer/accounts/accounts.ex +++ b/apps/explorer/lib/explorer/accounts/accounts.ex @@ -5,7 +5,7 @@ defmodule Explorer.Accounts do alias Comeonin.Bcrypt alias Ecto.Changeset - alias Explorer.Accounts.{User} + alias Explorer.Accounts.User alias Explorer.Accounts.User.{Authenticate, Registration} alias Explorer.Repo diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 91a5ad8764..1541fe2f2c 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3581,11 +3581,11 @@ defmodule Explorer.Chain do end @doc """ - Fetches bridges status for tokens. + Fetches bridged status for tokens and metadata. """ def fetch_tokens_bridged_status(token_addresses) do Enum.each(token_addresses, fn token_address_hash -> - created_from_factory_query = + created_from_int_tx_success_query = from( it in InternalTransaction, inner_join: t in assoc(it, :transaction), @@ -3593,104 +3593,145 @@ defmodule Explorer.Chain do where: t.status == ^1 ) - created_from_factory = - created_from_factory_query + created_from_int_tx_success = + created_from_int_tx_success_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 + created_from_tx_query = + from( + t in Transaction, + where: t.created_contract_address_hash == ^token_address_hash + ) - 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_from_tx = + created_from_tx_query + |> Repo.all() + |> Enum.count() > 0 - 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_from_int_tx_query = + from( + it in InternalTransaction, + where: it.created_contract_address_hash == ^token_address_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, 16) - - 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) + created_from_int_tx = + created_from_int_tx_query + |> Repo.all() + |> Enum.count() > 0 + + cond do + created_from_tx -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && !created_from_int_tx_success -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && created_from_int_tx_success -> + extract_bridged_token_metadata_wrapper(token_address_hash, created_from_int_tx_success) + + true -> + :ok end end) :ok end + defp extract_bridged_token_metadata_wrapper(token_address_hash, created_from_int_tx_success) do + multi_token_bridge_mediator = Application.get_env(:block_scout_web, :multi_token_bridge_mediator) + %{transaction_hash: transaction_hash} = created_from_int_tx_success + + 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 + extract_bridged_token_metadata( + token_address_hash, + multi_token_bridge_mediator, + multi_token_bridge_mediator_hash + ) + else + set_token_bridged_status(token_address_hash, false) + end + end + end + + defp extract_bridged_token_metadata(token_address_hash, multi_token_bridge_mediator, multi_token_bridge_mediator_hash) 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, 16) + + 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 + 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) diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index 753568eb9e..48ac03a1f5 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -171,8 +171,13 @@ defmodule Explorer.Chain.Block.Reward do if payout_key_hash == @empty_address do mining_key else - {:ok, payout_key} = Chain.string_to_address_hash(payout_key_hash) - payout_key + case Chain.string_to_address_hash(payout_key_hash) do + {:ok, payout_key} -> + payout_key + + _ -> + mining_key + end end else mining_key diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index e48ab5443b..84a774a314 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -35,6 +35,7 @@ defmodule Explorer.Chain.Token do * `contract_address_hash` - Address hash foreign key * `holder_count` - the number of `t:Explorer.Chain.Address.t/0` (except the burn address) that have a `t:Explorer.Chain.CurrentTokenBalance.t/0` `value > 0`. Can be `nil` when data not migrated. + * `bridged` - Flag for bridged tokens from other chain """ @type t :: %Token{ name: String.t(), diff --git a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex index ad5ada3ced..0dac1c66ed 100644 --- a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex @@ -8,6 +8,8 @@ defmodule Explorer.Token.InstanceMetadataRetriever do alias Explorer.SmartContract.Reader alias HTTPoison.{Error, Response} + @token_uri "c87b56dd" + @abi [ %{ "type" => "function", @@ -38,7 +40,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do def fetch_metadata(contract_address_hash, token_id) do # c87b56dd = keccak256(tokenURI(uint256)) - contract_functions = %{"c87b56dd" => [token_id]} + contract_functions = %{@token_uri => [token_id]} contract_address_hash |> query_contract(contract_functions) @@ -49,26 +51,26 @@ defmodule Explorer.Token.InstanceMetadataRetriever do Reader.query_contract(contract_address_hash, @abi, contract_functions) end - def fetch_json(%{"c87b56dd" => {:ok, [""]}}) do + def fetch_json(%{@token_uri => {:ok, [""]}}) do {:ok, %{error: @no_uri_error}} end - def fetch_json(%{"c87b56dd" => {:error, "(-32015) VM execution error."}}) do + def fetch_json(%{@token_uri => {:error, "(-32015) VM execution error."}}) do {:ok, %{error: @no_uri_error}} end - def fetch_json(%{"c87b56dd" => {:ok, ["http://" <> _ = token_uri]}}) do + def fetch_json(%{@token_uri => {:ok, ["http://" <> _ = token_uri]}}) do fetch_metadata(token_uri) end - def fetch_json(%{"c87b56dd" => {:ok, ["https://" <> _ = token_uri]}}) do + def fetch_json(%{@token_uri => {:ok, ["https://" <> _ = token_uri]}}) do fetch_metadata(token_uri) end - def fetch_json(%{"c87b56dd" => {:ok, ["data:application/json," <> json]}}) do + def fetch_json(%{@token_uri => {:ok, ["data:application/json," <> json]}}) do decoded_json = URI.decode(json) - fetch_json(%{"c87b56dd" => {:ok, [decoded_json]}}) + fetch_json(%{@token_uri => {:ok, [decoded_json]}}) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -78,7 +80,12 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:error, json} end - def fetch_json(%{"c87b56dd" => {:ok, [json]}}) do + def fetch_json(%{@token_uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}) do + ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid + fetch_metadata(ipfs_url) + end + + def fetch_json(%{@token_uri => {:ok, [json]}}) do {:ok, json} = decode_json(json) check_type(json) diff --git a/apps/indexer/lib/indexer/fetcher.ex b/apps/indexer/lib/indexer/fetcher.ex index b31e81c2e2..bfd992a986 100644 --- a/apps/indexer/lib/indexer/fetcher.ex +++ b/apps/indexer/lib/indexer/fetcher.ex @@ -50,7 +50,7 @@ defmodule Indexer.Fetcher do end end - def disabled?() do + def disabled? do Application.get_env(:indexer, __MODULE__, [])[:disabled?] == true end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex index 3f68f11cb6..2cd58825d7 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex @@ -15,7 +15,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do import Ecto.Query, only: [from: 2] import EthereumJSONRPC, only: [integer_to_quantity: 1] - alias EthereumJSONRPC.{FetchedBalances} + alias EthereumJSONRPC.FetchedBalances alias Explorer.{Chain, Repo} alias Explorer.Chain.Address alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily} diff --git a/apps/indexer/lib/indexer/fetcher/pending_transaction.ex b/apps/indexer/lib/indexer/fetcher/pending_transaction.ex index b8b2ab77f1..176d8dc9b7 100644 --- a/apps/indexer/lib/indexer/fetcher/pending_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/pending_transaction.ex @@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.PendingTransaction do alias Ecto.Changeset alias Explorer.Chain - alias Explorer.Chain.Cache.{Accounts} + alias Explorer.Chain.Cache.Accounts alias Indexer.Fetcher.PendingTransaction alias Indexer.Transform.Addresses diff --git a/mix.lock b/mix.lock index 921c8140cb..66a627ab58 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "118162367ef41448c9742ced8c8bc33ae2857d958d6b997e1db26402dd8c6f37"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, - "credo": {:hex, :credo, "1.1.2", "02b6422f3e659eb74b05aca3c20c1d8da0119a05ee82577a82e6c2938bf29f81", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd9322e9d391251ca3c4fac10ff0ce86ea4b99b3d9b34526ee2a7baa5928167d"}, + "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, "csv": {:hex, :csv, "2.3.1", "9ce11eff5a74a07baf3787b2b19dd798724d29a9c3a492a41df39f6af686da0e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "86626e1c89a4ad9a96d0d9c638f9e88c2346b89b4ba1611988594ebe72b5d5ee"}, "dataloader": {:hex, :dataloader, "1.0.7", "58351b335673cf40601429bfed6c11fece6ce7ad169b2ac0f0fe83e716587391", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "12bf66478e4a5085d09dc96932d058c206ee8c219cc7691d12a40dc35c8cefaa"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},