feat: Add filecoin robust addresses to proxy implementations (#11102)

* feat: Add filecoin robust addresses to proxy implementations

* Fix tests

* Fix tests

* Fix credo

* Process review comments

* Change naming
pull/11131/head
nikitosing 4 weeks ago committed by GitHub
parent 8ecde2e05d
commit 001ab94af4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      apps/block_scout_web/lib/block_scout_web.ex
  2. 10
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex
  4. 47
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  5. 22
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
  6. 15
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex
  7. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/mud_controller.ex
  8. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex
  9. 28
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
  10. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
  11. 39
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
  12. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex
  13. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex
  14. 20
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  15. 23
      apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex
  16. 15
      apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
  17. 22
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  18. 8
      apps/block_scout_web/lib/block_scout_web/paging_helper.ex
  19. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  20. 6
      apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex
  21. 18
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  22. 3
      apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex
  23. 16
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  24. 2
      apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex
  25. 2
      apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex
  26. 2
      apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex
  27. 24
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  28. 18
      apps/explorer/lib/explorer/chain.ex
  29. 26
      apps/explorer/lib/explorer/chain/address.ex
  30. 5
      apps/explorer/lib/explorer/chain/address/current_token_balance.ex
  31. 7
      apps/explorer/lib/explorer/chain/advanced_filter.ex
  32. 5
      apps/explorer/lib/explorer/chain/celo/epoch_reward.ex
  33. 6
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  34. 50
      apps/explorer/lib/explorer/chain/smart_contract.ex
  35. 76
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  36. 137
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  37. 13
      apps/explorer/lib/explorer/chain/token_transfer.ex
  38. 7
      apps/explorer/lib/explorer/etherscan/contracts.ex
  39. 7
      apps/explorer/lib/explorer/microservice_interfaces/metadata.ex
  40. 13
      apps/explorer/lib/explorer/smart_contract/helper.ex
  41. 53
      apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs
  42. 2
      apps/explorer/test/explorer/chain_test.exs

@ -29,6 +29,7 @@ defmodule BlockScoutWeb do
import BlockScoutWeb.ErrorHelper import BlockScoutWeb.ErrorHelper
import BlockScoutWeb.Routers.AccountRouter.Helpers, except: [static_path: 2] import BlockScoutWeb.Routers.AccountRouter.Helpers, except: [static_path: 2]
import Plug.Conn import Plug.Conn
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias BlockScoutWeb.Routers.AdminRouter.Helpers, as: AdminRoutes alias BlockScoutWeb.Routers.AdminRouter.Helpers, as: AdminRoutes
end end
@ -63,6 +64,8 @@ defmodule BlockScoutWeb do
import Explorer.Chain.CurrencyHelper, only: [divide_decimals: 2] import Explorer.Chain.CurrencyHelper, only: [divide_decimals: 2]
import BlockScoutWeb.Routers.WebRouter.Helpers, except: [static_path: 2] import BlockScoutWeb.Routers.WebRouter.Helpers, except: [static_path: 2]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
end end
end end
@ -80,6 +83,8 @@ defmodule BlockScoutWeb do
use Phoenix.Channel use Phoenix.Channel
use Gettext, backend: BlockScoutWeb.Gettext use Gettext, backend: BlockScoutWeb.Gettext
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
end end
end end

@ -49,18 +49,18 @@ defmodule BlockScoutWeb.AddressChannel do
end end
@transaction_associations [ @transaction_associations [
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [ to_address: [
:scam_badge, :scam_badge,
:names, :names,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
], ],
created_contract_address: [ created_contract_address: [
:scam_badge, :scam_badge,
:names, :names,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
] ]
] ++ ] ++
@chain_type_transaction_associations @chain_type_transaction_associations
@ -414,8 +414,8 @@ defmodule BlockScoutWeb.AddressChannel do
token_transfers token_transfers
|> Repo.preload([ |> Repo.preload([
[ [
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]
] ]
]), ]),
conn: nil conn: nil

@ -27,7 +27,7 @@ defmodule BlockScoutWeb.BlockChannel do
) do ) do
rendered_block = rendered_block =
BlockViewAPI.render("block.json", %{ BlockViewAPI.render("block.json", %{
block: block |> Repo.preload(miner: [:names, :smart_contract, :proxy_implementations]), block: block |> Repo.preload(miner: [:names, :smart_contract, proxy_implementations_association()]),
socket: nil socket: nil
}) })

@ -52,9 +52,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@transaction_necessity_by_association [ @transaction_necessity_by_association [
necessity_by_association: necessity_by_association:
%{ %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
:block => :optional :block => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association), |> Map.merge(@chain_type_transaction_necessity_by_association),
@ -63,8 +64,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@token_transfer_necessity_by_association [ @token_transfer_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
:block => :optional, :block => :optional,
:transaction => :optional, :transaction => :optional,
:token => :optional :token => :optional
@ -77,7 +78,6 @@ defmodule BlockScoutWeb.API.V2.AddressController do
:names => :optional, :names => :optional,
:scam_badge => :optional, :scam_badge => :optional,
:token => :optional, :token => :optional,
:proxy_implementations => :optional,
:signed_authorization => :optional :signed_authorization => :optional
}, },
api?: true api?: true
@ -88,16 +88,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@contract_address_preloads [ @contract_address_preloads [
:smart_contract, :smart_contract,
[contracts_creation_internal_transaction: :from_address], [contracts_creation_internal_transaction: :from_address],
[contracts_creation_transaction: :from_address], [contracts_creation_transaction: :from_address]
:proxy_implementations
] ]
_ -> _ ->
@contract_address_preloads [ @contract_address_preloads [
:smart_contract, :smart_contract,
:contracts_creation_internal_transaction, :contracts_creation_internal_transaction,
:contracts_creation_transaction, :contracts_creation_transaction
:proxy_implementations
] ]
end end
@ -115,14 +113,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do
account_address: [ account_address: [
:names, :names,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
] ]
] => :optional, ] => :optional,
[ [
associated_account_address: [ associated_account_address: [
:names, :names,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
] ]
] => :optional ] => :optional
}, },
@ -136,8 +134,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
fully_preloaded_address = fully_preloaded_address =
Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true) Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true)
{implementations, proxy_type} = implementations = SmartContractHelper.pre_fetch_implementations(fully_preloaded_address)
SmartContractHelper.pre_fetch_implementations(fully_preloaded_address)
CoinBalanceOnDemand.trigger_fetch(address) CoinBalanceOnDemand.trigger_fetch(address)
ContractCodeOnDemand.trigger_fetch(address) ContractCodeOnDemand.trigger_fetch(address)
@ -145,9 +142,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
conn conn
|> put_status(200) |> put_status(200)
|> render(:address, %{ |> render(:address, %{
address: fully_preloaded_address |> maybe_preload_ens_to_address(), address:
implementations: implementations, %Address{fully_preloaded_address | proxy_implementations: implementations} |> maybe_preload_ens_to_address()
proxy_type: proxy_type
}) })
end end
end end
@ -225,8 +221,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
:block => :optional, :block => :optional,
:token => :optional, :token => :optional,
:transaction => :optional :transaction => :optional
@ -297,9 +293,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
@ -333,7 +330,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do
|> paging_options() |> paging_options()
|> Keyword.merge(topic: formatted_topic) |> Keyword.merge(topic: formatted_topic)
|> Keyword.merge( |> Keyword.merge(
necessity_by_association: %{[address: [:names, :smart_contract, :proxy_implementations]] => :optional} necessity_by_association: %{
[address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
}
) )
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)
@ -378,7 +377,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[miner: :proxy_implementations] => :optional, [miner: proxy_implementations_association()] => :optional,
miner: :required, miner: :required,
nephews: :optional, nephews: :optional,
transactions: :optional, transactions: :optional,

@ -84,9 +84,10 @@ defmodule BlockScoutWeb.API.V2.BlockController do
@transaction_necessity_by_association [ @transaction_necessity_by_association [
necessity_by_association: necessity_by_association:
%{ %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
:block => :optional :block => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association) |> Map.merge(@chain_type_transaction_necessity_by_association)
@ -94,9 +95,10 @@ defmodule BlockScoutWeb.API.V2.BlockController do
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
@ -105,7 +107,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do
@block_params [ @block_params [
necessity_by_association: necessity_by_association:
%{ %{
[miner: [:names, :smart_contract, :proxy_implementations]] => :optional, [miner: [:names, :smart_contract, proxy_implementations_association()]] => :optional,
:uncles => :optional, :uncles => :optional,
:nephews => :optional, :nephews => :optional,
:rewards => :optional, :rewards => :optional,
@ -334,7 +336,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do
with {:ok, block} <- block_param_to_block(block_hash_or_number) do with {:ok, block} <- block_param_to_block(block_hash_or_number) do
full_options = full_options =
[ [
necessity_by_association: %{[address: [:names, :smart_contract, :proxy_implementations]] => :optional}, necessity_by_association: %{
[address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
},
api?: true api?: true
] ]
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
@ -415,7 +419,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do
with {:ok, reward_type_atom} <- celo_reward_type_to_atom(reward_type), with {:ok, reward_type_atom} <- celo_reward_type_to_atom(reward_type),
{:ok, block} <- {:ok, block} <-
block_param_to_block(block_hash_or_number) do block_param_to_block(block_hash_or_number) do
address_associations = [:names, :smart_contract, :proxy_implementations] address_associations = [:names, :smart_contract, proxy_implementations_association()]
full_options = full_options =
[ [

@ -1,5 +1,5 @@
defmodule BlockScoutWeb.API.V2.MainPageController do defmodule BlockScoutWeb.API.V2.MainPageController do
use Phoenix.Controller use BlockScoutWeb, :controller
alias Explorer.{Chain, PagingOptions} alias Explorer.{Chain, PagingOptions}
alias BlockScoutWeb.API.V2.{BlockView, OptimismView, TransactionView} alias BlockScoutWeb.API.V2.{BlockView, OptimismView, TransactionView}
@ -24,9 +24,10 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
necessity_by_association: necessity_by_association:
%{ %{
:block => :required, :block => :required,
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association), |> Map.merge(@chain_type_transaction_necessity_by_association),
paging_options: %PagingOptions{page_size: 6}, paging_options: %PagingOptions{page_size: 6},
@ -39,7 +40,11 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
blocks = blocks =
[paging_options: %PagingOptions{page_size: 4}, api?: true] [paging_options: %PagingOptions{page_size: 4}, api?: true]
|> Chain.list_blocks() |> Chain.list_blocks()
|> Repo.replica().preload([[miner: [:names, :smart_contract, :proxy_implementations]], :transactions, :rewards]) |> Repo.replica().preload([
[miner: [:names, :smart_contract, proxy_implementations_association()]],
:transactions,
:rewards
])
conn conn
|> put_status(200) |> put_status(200)

@ -37,7 +37,7 @@ defmodule BlockScoutWeb.API.V2.MudController do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
) )

@ -159,7 +159,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
) )

@ -23,8 +23,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
necessity_by_association: %{ necessity_by_association: %{
:contracts_creation_internal_transaction => :optional, :contracts_creation_internal_transaction => :optional,
[smart_contract: :smart_contract_additional_sources] => :optional, [smart_contract: :smart_contract_additional_sources] => :optional,
:contracts_creation_transaction => :optional, :contracts_creation_transaction => :optional
:proxy_implementations => :optional
}, },
api?: true api?: true
] ]
@ -39,12 +38,11 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
_ <- PublishHelper.sourcify_check(address_hash_string), _ <- PublishHelper.sourcify_check(address_hash_string),
{:not_found, {:ok, address}} <- {:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, false)} do {:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, false)} do
{implementations, proxy_type} = implementations = SmartContractHelper.pre_fetch_implementations(address)
SmartContractHelper.pre_fetch_implementations(address)
conn conn
|> put_status(200) |> put_status(200)
|> render(:smart_contract, %{address: address, implementations: implementations, proxy_type: proxy_type}) |> render(:smart_contract, %{address: %Address{address | proxy_implementations: implementations}})
end end
end end
@ -105,11 +103,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:not_found, {:ok, address}} <- {:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)}, {:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)},
{:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do {:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do
implementation_address_hash_strings = implementation_address_hash_strings = get_implementations_address_hashes(address)
address.smart_contract
|> Implementation.get_implementation(@api_true)
|> Tuple.to_list()
|> List.first()
functions = functions =
implementation_address_hash_strings implementation_address_hash_strings
@ -136,11 +130,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:not_found, {:ok, address}} <- {:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)}, {:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)},
{:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do {:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do
implementation_address_hash_strings = implementation_address_hash_strings = get_implementations_address_hashes(address)
address.smart_contract
|> Implementation.get_implementation(@api_true)
|> Tuple.to_list()
|> List.first()
functions = functions =
implementation_address_hash_strings implementation_address_hash_strings
@ -335,4 +325,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:ok, address_hash, smart_contract} {:ok, address_hash, smart_contract}
end end
end end
defp get_implementations_address_hashes(proxy_address) do
implementation =
proxy_address.smart_contract
|> Implementation.get_implementation(@api_true)
(implementation && implementation.address_hashes) || []
end
end end

@ -365,7 +365,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do
case Chain.nft_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do case Chain.nft_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do
{:ok, token_instance} -> {:ok, token_instance} ->
token_instance token_instance
|> Chain.select_repo(@api_true).preload(owner: [:names, :smart_contract, :proxy_implementations]) |> Chain.select_repo(@api_true).preload(owner: [:names, :smart_contract, proxy_implementations_association()])
|> Chain.put_owner_to_token_instance(token, @api_true) |> Chain.put_owner_to_token_instance(token, @api_true)
{:error, :not_found} -> {:error, :not_found} ->

@ -75,38 +75,45 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
:names, :names,
:token, :token,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
]
] => :optional,
[
from_address: [
:scam_badge,
:names,
:smart_contract,
proxy_implementations_association()
] ]
] => :optional, ] => :optional,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] =>
:optional,
[ [
to_address: [ to_address: [
:scam_badge, :scam_badge,
:names, :names,
:smart_contract, :smart_contract,
:proxy_implementations proxy_implementations_association()
] ]
] => :optional ] => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association) |> Map.merge(@chain_type_transaction_necessity_by_association)
@token_transfers_necessity_by_association %{ @token_transfers_necessity_by_association %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
@token_transfers_in_transaction_necessity_by_association %{ @token_transfers_in_transaction_necessity_by_association %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
token: :required token: :required
} }
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
@ -495,7 +502,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
@ -573,8 +580,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)
@ -600,8 +607,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)

@ -30,7 +30,7 @@ defmodule BlockScoutWeb.API.V2.ValidatorController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)
@ -81,7 +81,7 @@ defmodule BlockScoutWeb.API.V2.ValidatorController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)

@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional, [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional,
block: :optional block: :optional
}, },
api?: true api?: true

@ -25,16 +25,7 @@ defmodule BlockScoutWeb.SmartContractController do
{:custom_abi, false} <- {:custom_abi, is_custom_abi}, {:custom_abi, false} <- {:custom_abi, is_custom_abi},
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
implementation_address_hash_string = implementation_address_hash_string = implementation_address_hash(contract_type, address)
if contract_type == "proxy" do
address.smart_contract
|> Implementation.get_implementation()
|> Tuple.to_list()
|> List.first()
|> List.first() || burn_address_hash_string()
else
burn_address_hash_string()
end
functions = functions =
if action == "write" do if action == "write" do
@ -107,6 +98,15 @@ defmodule BlockScoutWeb.SmartContractController do
def index(conn, _), do: not_found(conn) def index(conn, _), do: not_found(conn)
defp implementation_address_hash(contract_type, address) do
if contract_type == "proxy" do
implementation = Implementation.get_implementation(address.smart_contract)
(implementation && implementation.address_hashes |> List.first()) || burn_address_hash_string()
else
burn_address_hash_string()
end
end
defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do
with custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string), with custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string),
false <- is_nil(custom_abi), false <- is_nil(custom_abi),

@ -9,9 +9,9 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
alias Explorer.Chain.{Data, InternalTransaction, Log, TokenTransfer, Transaction} alias Explorer.Chain.{Data, InternalTransaction, Log, TokenTransfer, Transaction}
alias HTTPoison.Response alias HTTPoison.Response
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1]
import Explorer.Utility.Microservice, only: [base_url: 2, check_enabled: 2] import Explorer.Utility.Microservice, only: [base_url: 2, check_enabled: 2]
require Logger require Logger
@post_timeout :timer.minutes(5) @post_timeout :timer.minutes(5)
@ -20,9 +20,10 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
@items_limit 50 @items_limit 50
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] =>
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
@ -176,8 +177,8 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)
@ -212,7 +213,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
} }
] ]
|> Keyword.merge(@api_true) |> Keyword.merge(@api_true)
@ -234,7 +235,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
log_options = log_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[address: [:names, :smart_contract, :proxy_implementations]] => :optional [address: [:names, :smart_contract, proxy_implementations_association()]] => :optional
}, },
limit: @items_limit limit: @items_limit
] ]
@ -254,8 +255,8 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
token_transfer_options = token_transfer_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional,
:token => :optional :token => :optional
} }
] ]
@ -303,7 +304,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
], ],

@ -5,6 +5,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do
import Explorer.PagingOptions, only: [default_paging_options: 0] import Explorer.PagingOptions, only: [default_paging_options: 0]
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.Chain.Transaction.StateChange alias Explorer.Chain.Transaction.StateChange
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions, Repo}
@ -69,16 +70,16 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do
|> Enum.find(&(&1.hash == transaction.hash)) |> Enum.find(&(&1.hash == transaction.hash))
|> Repo.preload( |> Repo.preload(
token_transfers: [ token_transfers: [
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]
], ],
internal_transactions: [ internal_transactions: [
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]
], ],
block: [miner: [:names, :smart_contract, :proxy_implementations]], block: [miner: [:names, :smart_contract, proxy_implementations_association()]],
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]
) )
previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number) previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number)

@ -27,6 +27,8 @@ defmodule BlockScoutWeb.Notifier do
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
alias Phoenix.View alias Phoenix.View
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
@check_broadcast_sequence_period 500 @check_broadcast_sequence_period 500
case Application.compile_env(:explorer, :chain_type) do case Application.compile_env(:explorer, :chain_type) do
@ -181,8 +183,18 @@ defmodule BlockScoutWeb.Notifier do
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:token, :token,
:transaction, :transaction,
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] :scam_badge,
:names,
:smart_contract,
proxy_implementations_association()
],
to_address: [
:scam_badge,
:names,
:smart_contract,
proxy_implementations_association()
]
]) ])
) )
@ -205,9 +217,9 @@ defmodule BlockScoutWeb.Notifier do
def handle_event({:chain_event, :transactions, :realtime, transactions}) do def handle_event({:chain_event, :transactions, :realtime, transactions}) do
base_preloads = [ base_preloads = [
:block, :block,
created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]
] ]
preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads

@ -3,6 +3,8 @@ defmodule BlockScoutWeb.PagingHelper do
Helper for fetching filters and other url query parameters Helper for fetching filters and other url query parameters
""" """
import Explorer.Chain, only: [string_to_transaction_hash: 1] import Explorer.Chain, only: [string_to_transaction_hash: 1]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.Chain.Stability.Validator, as: ValidatorStability alias Explorer.Chain.Stability.Validator, as: ValidatorStability
alias Explorer.Chain.Transaction alias Explorer.Chain.Transaction
alias Explorer.{Helper, PagingOptions, SortingHelper} alias Explorer.{Helper, PagingOptions, SortingHelper}
@ -158,7 +160,7 @@ defmodule BlockScoutWeb.PagingHelper do
[ [
necessity_by_association: %{ necessity_by_association: %{
:transactions => :optional, :transactions => :optional,
[miner: [:names, :smart_contract, :proxy_implementations]] => :optional, [miner: [:names, :smart_contract, proxy_implementations_association()]] => :optional,
:nephews => :required, :nephews => :required,
:rewards => :optional :rewards => :optional
}, },
@ -169,7 +171,7 @@ defmodule BlockScoutWeb.PagingHelper do
[ [
necessity_by_association: %{ necessity_by_association: %{
:transactions => :optional, :transactions => :optional,
[miner: [:names, :smart_contract, :proxy_implementations]] => :optional, [miner: [:names, :smart_contract, proxy_implementations_association()]] => :optional,
:rewards => :optional :rewards => :optional
}, },
block_type: "Reorg" block_type: "Reorg"
@ -184,7 +186,7 @@ defmodule BlockScoutWeb.PagingHelper do
do: [ do: [
necessity_by_association: %{ necessity_by_association: %{
:transactions => :optional, :transactions => :optional,
[miner: [:names, :smart_contract, :proxy_implementations]] => :optional, [miner: [:names, :smart_contract, proxy_implementations_association()]] => :optional,
:rewards => :optional :rewards => :optional
}, },
block_type: "Block" block_type: "Block"

@ -124,9 +124,9 @@
<% end %> <% end %>
<!-- Implementation --> <!-- Implementation -->
<%= if @is_proxy do %> <%= if @is_proxy do %>
<% {implementation_addresses, implementation_names, _proxy_type} = Implementation.get_implementation(@address.smart_contract) %> <% implementation = Implementation.get_implementation(@address.smart_contract) %>
<% implementation_address_ = Enum.at(implementation_addresses, 0) %> <% implementation_address_ = implementation && Enum.at(implementation.address_hashes, 0) %>
<% name = Enum.at(implementation_names, 0) %> <% name = implementation && Enum.at(implementation.names, 0) %>
<% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %> <% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>
<dl class="row"> <dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted"> <dt class="col-sm-4 col-md-4 col-lg-3 text-muted">

@ -1,4 +1,6 @@
defmodule BlockScoutWeb.Account.API.V2.UserView do defmodule BlockScoutWeb.Account.API.V2.UserView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Account.API.V2.AccountView alias BlockScoutWeb.Account.API.V2.AccountView
alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.API.V2.Helper
alias Ecto.Changeset alias Ecto.Changeset
@ -198,7 +200,9 @@ defmodule BlockScoutWeb.Account.API.V2.UserView do
defp get_address(address_hash) do defp get_address(address_hash) do
case Chain.hash_to_address( case Chain.hash_to_address(
address_hash, address_hash,
[necessity_by_association: %{smart_contract: :optional, proxy_implementations: :optional}], [
necessity_by_association: %{:smart_contract => :optional, proxy_implementations_association() => :optional}
],
false false
) do ) do
{:ok, address} -> address {:ok, address} -> address

@ -16,8 +16,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do
ApiView.render("message.json", assigns) ApiView.render("message.json", assigns)
end end
def render("address.json", %{address: address, implementations: implementations, proxy_type: proxy_type, conn: conn}) do def render("address.json", %{address: address, conn: conn}) do
prepare_address(address, conn, implementations, proxy_type) prepare_address(address, conn)
end end
def render("token_balances.json", %{token_balances: token_balances}) do def render("token_balances.json", %{token_balances: token_balances}) do
@ -90,8 +90,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do
@doc """ @doc """
Prepares address properties for rendering in /addresses and /addresses/:address_hash_param API v2 endpoints Prepares address properties for rendering in /addresses and /addresses/:address_hash_param API v2 endpoints
""" """
@spec prepare_address(Address.t(), Plug.Conn.t() | nil, list(), String.t() | nil) :: map() @spec prepare_address(Address.t(), Plug.Conn.t() | nil) :: map()
def prepare_address(address, conn \\ nil, implementations \\ [], proxy_type \\ nil) do def prepare_address(address, conn \\ nil) do
base_info = Helper.address_with_info(conn, address, address.hash, true) base_info = Helper.address_with_info(conn, address, address.hash, true)
balance = address.fetched_coin_balance && address.fetched_coin_balance.value balance = address.fetched_coin_balance && address.fetched_coin_balance.value
@ -121,17 +121,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do
"has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true) "has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true)
}) })
result =
if Enum.empty?(implementations) do
extended_info extended_info
else
Map.merge(extended_info, %{
"proxy_type" => proxy_type,
"implementations" => implementations
})
end
result
|> chain_type_fields(%{address: creation_transaction && creation_transaction.from_address, field_prefix: "creator"}) |> chain_type_fields(%{address: creation_transaction && creation_transaction.from_address, field_prefix: "creator"})
end end

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do
@moduledoc """ @moduledoc """
View functions for rendering Celo-related data in JSON format. View functions for rendering Celo-related data in JSON format.
""" """
use BlockScoutWeb, :view
require Logger require Logger
@ -20,7 +21,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
] ]

@ -64,20 +64,16 @@ defmodule BlockScoutWeb.API.V2.Helper do
def address_with_info(%Address{} = address, _address_hash) do def address_with_info(%Address{} = address, _address_hash) do
smart_contract? = Address.smart_contract?(address) smart_contract? = Address.smart_contract?(address)
{proxy_implementations, implementation_address_hashes, implementation_names, proxy_type} = proxy_implementations =
case address.proxy_implementations do case address.proxy_implementations do
%NotLoaded{} -> %NotLoaded{} ->
{nil, [], [], nil} nil
nil -> nil ->
{nil, [], [], nil} nil
proxy_implementations -> proxy_implementations ->
address_hashes = proxy_implementations.address_hashes proxy_implementations
names = proxy_implementations.names
proxy_type = proxy_implementations.proxy_type
{proxy_implementations, address_hashes, names, proxy_type}
end end
%{ %{
@ -85,8 +81,8 @@ defmodule BlockScoutWeb.API.V2.Helper do
"is_contract" => smart_contract?, "is_contract" => smart_contract?,
"name" => address_name(address), "name" => address_name(address),
"is_scam" => address_marked_as_scam?(address), "is_scam" => address_marked_as_scam?(address),
"proxy_type" => proxy_type, "proxy_type" => proxy_implementations && proxy_implementations.proxy_type,
"implementations" => Proxy.proxy_object_info(implementation_address_hashes, implementation_names), "implementations" => Proxy.proxy_object_info(proxy_implementations),
"is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations), "is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations),
"ens_domain_name" => address.ens_domain_name, "ens_domain_name" => address.ens_domain_name,
"metadata" => address.metadata "metadata" => address.metadata

@ -232,7 +232,7 @@ defmodule BlockScoutWeb.API.V2.OptimismView do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
], ],

@ -90,7 +90,7 @@ defmodule BlockScoutWeb.API.V2.PolygonEdgeView do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
], ],

@ -62,7 +62,7 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:smart_contract => :optional, :smart_contract => :optional,
:proxy_implementations => :optional proxy_implementations_association() => :optional
}, },
api?: true api?: true
) )

@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Address, SmartContract, SmartContractAdditionalSource} alias Explorer.Chain.{Address, SmartContract, SmartContractAdditionalSource}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.Helper, as: SmartContractHelper alias Explorer.SmartContract.Helper, as: SmartContractHelper
alias Explorer.Visualize.Sol2uml alias Explorer.Visualize.Sol2uml
@ -26,13 +27,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
%{"items" => Enum.map(smart_contracts, &prepare_smart_contract_for_list/1), "next_page_params" => next_page_params} %{"items" => Enum.map(smart_contracts, &prepare_smart_contract_for_list/1), "next_page_params" => next_page_params}
end end
def render("smart_contract.json", %{ def render("smart_contract.json", %{address: address, conn: conn}) do
address: address, prepare_smart_contract(address, conn)
implementations: implementations,
proxy_type: proxy_type,
conn: conn
}) do
prepare_smart_contract(address, implementations, proxy_type, conn)
end end
def render("read_functions.json", %{functions: functions}) do def render("read_functions.json", %{functions: functions}) do
@ -156,9 +152,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
# credo:disable-for-next-line # credo:disable-for-next-line
def prepare_smart_contract( def prepare_smart_contract(
%Address{smart_contract: %SmartContract{} = smart_contract} = address, %Address{smart_contract: %SmartContract{} = smart_contract, proxy_implementations: implementations} = address,
implementations,
proxy_type,
conn conn
) do ) do
bytecode_twin = SmartContract.get_address_verified_bytecode_twin_contract(address.hash, @api_true) bytecode_twin = SmartContract.get_address_verified_bytecode_twin_contract(address.hash, @api_true)
@ -202,8 +196,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"has_methods_write" => write_methods?, "has_methods_write" => write_methods?,
"has_methods_read_proxy" => is_proxy, "has_methods_read_proxy" => is_proxy,
"has_methods_write_proxy" => is_proxy && write_methods?, "has_methods_write_proxy" => is_proxy && write_methods?,
"proxy_type" => proxy_type, "proxy_type" => implementations && implementations.proxy_type,
"implementations" => implementations, "implementations" => Proxy.proxy_object_info(implementations),
"sourcify_repo_url" => "sourcify_repo_url" =>
if(smart_contract.verified_via_sourcify && smart_contract_verified, if(smart_contract.verified_via_sourcify && smart_contract_verified,
do: AddressContractView.sourcify_repo_url(address.hash, smart_contract.partially_verified) do: AddressContractView.sourcify_repo_url(address.hash, smart_contract.partially_verified)
@ -240,15 +234,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
|> chain_type_fields(%{address_hash: verified_twin_address_hash, field_prefix: "verified_twin"}) |> chain_type_fields(%{address_hash: verified_twin_address_hash, field_prefix: "verified_twin"})
end end
def prepare_smart_contract(address, implementations, proxy_type, conn) do def prepare_smart_contract(%Address{proxy_implementations: implementations} = address, conn) do
read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash) read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash)
write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash) write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash)
%{ %{
"has_custom_methods_read" => read_custom_abi?, "has_custom_methods_read" => read_custom_abi?,
"has_custom_methods_write" => write_custom_abi?, "has_custom_methods_write" => write_custom_abi?,
"proxy_type" => proxy_type, "proxy_type" => implementations && implementations.proxy_type,
"implementations" => implementations "implementations" => Proxy.proxy_object_info(implementations)
} }
|> Map.merge(bytecode_info(address)) |> Map.merge(bytecode_info(address))
end end

@ -85,6 +85,7 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand} alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand}
alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type} alias Explorer.Chain.InternalTransaction.{CallType, Type}
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Market.MarketHistoryCache alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
@ -391,7 +392,12 @@ defmodule Explorer.Chain do
base base
else else
base base
|> preload(transaction: [from_address: [:proxy_implementations], to_address: [:proxy_implementations]]) |> preload(
transaction: [
from_address: ^Implementation.proxy_implementations_association(),
to_address: ^Implementation.proxy_implementations_association()
]
)
end end
preloaded_query preloaded_query
@ -1113,7 +1119,7 @@ defmodule Explorer.Chain do
|> Keyword.get(:necessity_by_association, %{}) |> Keyword.get(:necessity_by_association, %{})
|> Map.merge(%{ |> Map.merge(%{
[smart_contract: :smart_contract_additional_sources] => :optional, [smart_contract: :smart_contract_additional_sources] => :optional,
:proxy_implementations => :optional Implementation.proxy_implementations_association() => :optional
}) })
query = query =
@ -4393,7 +4399,7 @@ defmodule Explorer.Chain do
|> Instance.address_to_unique_token_instances() |> Instance.address_to_unique_token_instances()
|> Instance.page_token_instance(paging_options) |> Instance.page_token_instance(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> preload([_], owner: [:names, :smart_contract, :proxy_implementations]) |> preload([_], owner: [:names, :smart_contract, ^Implementation.proxy_implementations_association()])
|> select_repo(options).all() |> select_repo(options).all()
|> Enum.map(&put_owner_to_token_instance(&1, token, options)) |> Enum.map(&put_owner_to_token_instance(&1, token, options))
end end
@ -4424,7 +4430,11 @@ defmodule Explorer.Chain do
owner_address_hash, owner_address_hash,
options options
|> Keyword.merge( |> Keyword.merge(
necessity_by_association: %{names: :optional, smart_contract: :optional, proxy_implementations: :optional} necessity_by_association: %{
:names => :optional,
:smart_contract => :optional,
Implementation.proxy_implementations_association() => :optional
}
) )
) )

@ -138,12 +138,12 @@ defmodule Explorer.Chain.Address do
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.Helper, as: ExplorerHelper alias Explorer.Helper, as: ExplorerHelper
alias Explorer.Chain.Cache.{Accounts, NetVersion} alias Explorer.Chain.Cache.{Accounts, NetVersion}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.EIP7702 alias Explorer.Chain.SmartContract.Proxy.EIP7702
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Chain.{Address, Data, Hash, InternalTransaction, Transaction} alias Explorer.Chain.{Address, Data, Hash, InternalTransaction, Transaction}
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions, Repo}
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a @optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a
@chain_type_optional_attrs (case Application.compile_env(:explorer, :chain_type) do @chain_type_optional_attrs (case Application.compile_env(:explorer, :chain_type) do
:filecoin -> :filecoin ->
@ -552,7 +552,7 @@ defmodule Explorer.Chain.Address do
from(a in Address, from(a in Address,
where: a.fetched_coin_balance > ^0, where: a.fetched_coin_balance > ^0,
order_by: [desc: a.fetched_coin_balance, asc: a.hash], order_by: [desc: a.fetched_coin_balance, asc: a.hash],
preload: [:names, :smart_contract, :proxy_implementations], preload: [:names, :smart_contract, ^proxy_implementations_association()],
select: {a, a.transactions_count} select: {a, a.transactions_count}
) )
@ -653,26 +653,6 @@ defmodule Explorer.Chain.Address do
) )
end end
@doc """
Prepares implementations object and proxy type from address
"""
@spec parse_implementation_and_proxy_type(__MODULE__.t()) :: {list(), String.t() | nil}
def parse_implementation_and_proxy_type(address) do
with %__MODULE__{
proxy_implementations: %Implementation{
address_hashes: address_hashes,
names: names,
proxy_type: proxy_type
}
} <- address,
false <- address_hashes && Enum.empty?(address_hashes) do
{Proxy.proxy_object_info(address_hashes, names), proxy_type}
else
_ ->
{[], nil}
end
end
@doc """ @doc """
Retrieves the creation transaction for a given address. Retrieves the creation transaction for a given address.

@ -11,6 +11,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query, only: [from: 2, limit: 2, offset: 2, order_by: 3, preload: 2, dynamic: 2] import Ecto.Query, only: [from: 2, limit: 2, offset: 2, order_by: 3, preload: 2, dynamic: 2]
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, CurrencyHelper, Hash, Token} alias Explorer.Chain.{Address, Block, CurrencyHelper, Hash, Token}
@ -85,7 +86,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
def token_holders_ordered_by_value(token_contract_address_hash, options \\ []) do def token_holders_ordered_by_value(token_contract_address_hash, options \\ []) do
token_contract_address_hash token_contract_address_hash
|> token_holders_ordered_by_value_query_without_address_preload(options) |> token_holders_ordered_by_value_query_without_address_preload(options)
|> preload(address: [:names, :smart_contract, :proxy_implementations]) |> preload(address: [:names, :smart_contract, ^proxy_implementations_association()])
end end
@doc """ @doc """
@ -131,7 +132,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
_ -> _ ->
token_contract_address_hash token_contract_address_hash
|> token_holders_by_token_id_query(token_id) |> token_holders_by_token_id_query(token_id)
|> preload(address: [:names, :smart_contract, :proxy_implementations]) |> preload(address: [:names, :smart_contract, ^proxy_implementations_association()])
|> order_by([tb], desc: :value, desc: :address_hash) |> order_by([tb], desc: :value, desc: :address_hash)
|> Chain.page_token_balances(paging_options) |> Chain.page_token_balances(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)

@ -6,6 +6,7 @@ defmodule Explorer.Chain.AdvancedFilter do
use Explorer.Schema use Explorer.Schema
import Ecto.Query import Ecto.Query
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.{Chain, Helper, PagingOptions} alias Explorer.{Chain, Helper, PagingOptions}
alias Explorer.Chain.{Address, Data, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction} alias Explorer.Chain.{Address, Data, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction}
@ -115,9 +116,9 @@ defmodule Explorer.Chain.AdvancedFilter do
|> Enum.sort(&sort_function/2) |> Enum.sort(&sort_function/2)
|> take_page_size(paging_options) |> take_page_size(paging_options)
|> Chain.select_repo(options).preload( |> Chain.select_repo(options).preload(
from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()],
created_contract_address: [:names, :smart_contract, :proxy_implementations] created_contract_address: [:names, :smart_contract, proxy_implementations_association()]
) )
end end

@ -8,6 +8,7 @@ defmodule Explorer.Chain.Celo.EpochReward do
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2]
import Explorer.Chain, only: [select_repo: 1] import Explorer.Chain, only: [select_repo: 1]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.Chain.Celo.EpochReward alias Explorer.Chain.Celo.EpochReward
alias Explorer.Chain.{Block, Hash, TokenTransfer} alias Explorer.Chain.{Block, Hash, TokenTransfer}
@ -101,8 +102,8 @@ defmodule Explorer.Chain.Celo.EpochReward do
select: {tt.log_index, tt}, select: {tt.log_index, tt},
preload: [ preload: [
:token, :token,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, ^proxy_implementations_association()]],
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, ^proxy_implementations_association()]]
] ]
) )

@ -8,6 +8,8 @@ defmodule Explorer.Chain.InternalTransaction do
alias Explorer.Chain.DenormalizationHelper alias Explorer.Chain.DenormalizationHelper
alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type} alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type}
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
@typep paging_options :: {:paging_options, PagingOptions.t()} @typep paging_options :: {:paging_options, PagingOptions.t()}
@typep api? :: {:api?, true | false} @typep api? :: {:api?, true | false}
@ -832,8 +834,8 @@ defmodule Explorer.Chain.InternalTransaction do
preloads = preloads =
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:block, :block,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]],
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]]
]) ])
__MODULE__ __MODULE__

@ -644,27 +644,7 @@ defmodule Explorer.Chain.SmartContract do
@spec compose_address_for_unverified_smart_contract(map(), any()) :: map() @spec compose_address_for_unverified_smart_contract(map(), any()) :: map()
def compose_address_for_unverified_smart_contract(%{smart_contract: smart_contract} = address_result, options) def compose_address_for_unverified_smart_contract(%{smart_contract: smart_contract} = address_result, options)
when is_nil(smart_contract) do when is_nil(smart_contract) do
smart_contract = %{
updated: %__MODULE__{
address_hash: address_result.hash
},
implementation_updated_at: nil,
implementation_address_fetched?: false,
refetch_necessity_checked?: false
}
{implementation_address_hash, _names, _proxy_type} =
Implementation.get_implementation(
smart_contract,
Keyword.put(options, :proxy_without_abi?, true)
)
implementation_smart_contract =
implementation_address_hash
|> Proxy.implementation_to_smart_contract(options)
address_verified_bytecode_twin_contract = address_verified_bytecode_twin_contract =
implementation_smart_contract ||
get_address_verified_bytecode_twin_contract(address_result.hash, options).verified_contract get_address_verified_bytecode_twin_contract(address_result.hash, options).verified_contract
if address_verified_bytecode_twin_contract do if address_verified_bytecode_twin_contract do
@ -677,10 +657,10 @@ defmodule Explorer.Chain.SmartContract do
def compose_address_for_unverified_smart_contract(address_result, _hash, _options), do: address_result def compose_address_for_unverified_smart_contract(address_result, _hash, _options), do: address_result
def single_implementation_smart_contract_from_proxy(proxy_hash, options) do def single_implementation_smart_contract_from_proxy(proxy_hash, options) do
{implementation_address_hashes, _names, _proxy_type} = Implementation.get_implementation(proxy_hash, options) implementation = Implementation.get_implementation(proxy_hash, options)
if implementation_address_hashes && Enum.count(implementation_address_hashes) == 1 do if implementation && Enum.count(implementation.address_hashes) == 1 do
implementation_address_hashes implementation.address_hashes
|> Enum.at(0) |> Enum.at(0)
|> Proxy.implementation_to_smart_contract(options) |> Proxy.implementation_to_smart_contract(options)
else else
@ -1117,24 +1097,24 @@ defmodule Explorer.Chain.SmartContract do
@doc """ @doc """
Gets smart-contract ABI from the DB for the given address hash of smart-contract Gets smart-contract ABI from the DB for the given address hash of smart-contract
""" """
@spec get_smart_contract_abi(String.t(), any()) :: any() @spec get_smart_contract_abi(String.t() | Hash.Address.t(), any()) :: any()
def get_smart_contract_abi(address_hash_string, options \\ []) def get_smart_contract_abi(address_hash, options \\ [])
def get_smart_contract_abi(address_hash_string, options) when is_binary(address_hash_string) do
case Chain.string_to_address_hash(address_hash_string) do
{:ok, address_hash} ->
get_smart_contract_abi(address_hash, options)
def get_smart_contract_abi(address_hash_string, options)
when not is_nil(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{smart_contract, _} =
address_hash
|> address_hash_to_smart_contract_with_bytecode_twin(options, false),
false <- is_nil(smart_contract) do
smart_contract
|> Map.get(:abi)
else
_ -> _ ->
[] []
end end
end end
def get_smart_contract_abi(%Hash{} = address_hash, options) do
{smart_contract, _} = address_hash_to_smart_contract_with_bytecode_twin(address_hash, options, false)
(smart_contract && smart_contract.abi) || []
end
def get_smart_contract_abi(address_hash_string, _) when is_nil(address_hash_string) do def get_smart_contract_abi(address_hash_string, _) when is_nil(address_hash_string) do
[] []
end end

@ -93,21 +93,11 @@ defmodule Explorer.Chain.SmartContract.Proxy do
if options[:skip_implementation_fetch?] do if options[:skip_implementation_fetch?] do
false false
else else
{implementation_address_hash_strings, _implementation_names, _proxy_type} = implementation = get_implementation(smart_contract, options)
get_implementation(smart_contract, options)
with false <- is_nil(implementation),
with false <- is_nil(implementation_address_hash_strings), false <- Enum.empty?(implementation.address_hashes) do
false <- Enum.empty?(implementation_address_hash_strings) do has_not_burn_address_hash?(implementation.address_hashes, burn_address_hash)
implementation_address_hash_strings
|> Enum.reduce_while(false, fn implementation_address_hash_string, acc ->
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
{:halt, true}
else
_ ->
{:cont, acc}
end
end)
else else
_ -> _ ->
false false
@ -116,6 +106,14 @@ defmodule Explorer.Chain.SmartContract.Proxy do
end end
end end
@spec has_not_burn_address_hash?([Hash.Address.t()], Hash.Address.t()) :: boolean()
defp has_not_burn_address_hash?(address_hashes, burn_address_hash) do
address_hashes
|> Enum.reduce_while(false, fn implementation_address_hash, acc ->
if implementation_address_hash.bytes == burn_address_hash.bytes, do: {:cont, acc}, else: {:halt, true}
end)
end
@doc """ @doc """
Decodes and formats an address output from a smart contract ABI. Decodes and formats an address output from a smart contract ABI.
@ -158,11 +156,12 @@ defmodule Explorer.Chain.SmartContract.Proxy do
options options
) )
when not is_nil(proxy_address_hash) and not is_nil(abi) do when not is_nil(proxy_address_hash) and not is_nil(abi) do
{implementation_address_hash_strings, _names, _proxy_type} = get_implementation(smart_contract, options) implementation = get_implementation(smart_contract, options)
implementation_address_hash_strings ((implementation && implementation.address_hashes) ||
|> Enum.reduce([], fn implementation_address_hash_string, acc -> [])
SmartContract.get_smart_contract_abi(implementation_address_hash_string) ++ acc |> Enum.reduce([], fn implementation_address_hash, acc ->
SmartContract.get_smart_contract_abi(implementation_address_hash) ++ acc
end) end)
end end
@ -556,25 +555,54 @@ defmodule Explorer.Chain.SmartContract.Proxy do
A list of maps containing information about the proxy object. A list of maps containing information about the proxy object.
""" """
@spec proxy_object_info([String.t() | Hash.Address.t()], [String.t() | nil]) :: [map()] @spec proxy_object_info(Implementation.t() | nil) :: [map()]
def proxy_object_info([], []), do: [] def proxy_object_info(nil), do: []
def proxy_object_info(proxy_implementation) do
implementations_info = prepare_implementations(proxy_implementation)
implementation_addresses = proxy_implementation.address_hashes
implementation_names = proxy_implementation.names
def proxy_object_info(implementation_addresses, implementation_names) do
implementation_addresses implementation_addresses
|> Enum.zip(implementation_names) |> Enum.zip(implementation_names)
|> Enum.reduce([], fn {address, name}, acc -> |> Enum.reduce([], fn {address, name}, acc ->
case address do case address do
%Hash{} = address_hash -> %Hash{} = address_hash ->
[%{"address" => Address.checksum(address_hash), "name" => name} | acc] [
%{"address" => Address.checksum(address_hash), "name" => name} |> chain_type_fields(implementations_info)
| acc
]
_ -> _ ->
with {:ok, address_hash} <- string_to_address_hash(address), with {:ok, address_hash} <- string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do checksummed_address <- Address.checksum(address_hash) do
[%{"address" => checksummed_address, "name" => name} | acc] [%{"address" => checksummed_address, "name" => name} |> chain_type_fields(implementations_info) | acc]
else else
_ -> acc _ -> acc
end end
end end
end) end)
end end
if Application.compile_env(:explorer, :chain_type) == :filecoin do
def chain_type_fields(%{"address" => address_hash} = address, implementations_info) do
Map.put(address, "filecoin_robust_address", implementations_info[address_hash])
end
def prepare_implementations(%Implementation{addresses: [_ | _] = addresses}) do
Enum.into(addresses, %{}, fn address -> {Address.checksum(address.hash), address.filecoin_robust} end)
end
def prepare_implementations(_) do
%{}
end
else
def chain_type_fields(address, _proxy_implementations) do
address
end
def prepare_implementations(_implementations_info) do
:ignore
end
end
end end

@ -13,12 +13,10 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
select: 3 select: 3
] ]
import Explorer.Chain, only: [select_repo: 1, string_to_address_hash: 1] alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Explorer.Repo
alias Timex.Duration alias Timex.Duration
@burn_address_hash_string "0x0000000000000000000000000000000000000000" @burn_address_hash_string "0x0000000000000000000000000000000000000000"
@ -58,6 +56,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
field(:address_hashes, {:array, Hash.Address}, null: false) field(:address_hashes, {:array, Hash.Address}, null: false)
field(:names, {:array, :string}, null: false) field(:names, {:array, :string}, null: false)
has_many(:addresses, Address, foreign_key: :hash, references: :address_hashes)
belongs_to( belongs_to(
:address, :address,
Address, Address,
@ -88,7 +88,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
def get_proxy_implementations(proxy_address_hash, options \\ []) do def get_proxy_implementations(proxy_address_hash, options \\ []) do
proxy_address_hash proxy_address_hash
|> get_proxy_implementations_query() |> get_proxy_implementations_query()
|> select_repo(options).one() |> Chain.select_repo(options).one()
end end
@doc """ @doc """
@ -102,7 +102,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
def get_proxy_implementations_for_multiple_proxies(proxy_address_hashes, options) do def get_proxy_implementations_for_multiple_proxies(proxy_address_hashes, options) do
proxy_address_hashes proxy_address_hashes
|> get_proxy_implementations_by_multiple_hashes_query() |> get_proxy_implementations_by_multiple_hashes_query()
|> select_repo(options).all() |> Chain.select_repo(options).all()
end end
@doc """ @doc """
@ -113,7 +113,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
proxy_address_hash proxy_address_hash
|> get_proxy_implementations_query() |> get_proxy_implementations_query()
|> select([p], p.updated_at) |> select([p], p.updated_at)
|> select_repo(options).one() |> Chain.select_repo(options).one()
end end
defp get_proxy_implementations_query(proxy_address_hash) do defp get_proxy_implementations_query(proxy_address_hash) do
@ -133,7 +133,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
@doc """ @doc """
Returns implementation address, name and proxy type for the given SmartContract Returns implementation address, name and proxy type for the given SmartContract
""" """
@spec get_implementation(any(), any()) :: {any(), any(), atom() | nil} @spec get_implementation(any(), any()) :: t() | nil
def get_implementation(smart_contract, options \\ []) def get_implementation(smart_contract, options \\ [])
def get_implementation( def get_implementation(
@ -226,35 +226,29 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
case Task.yield(get_implementation_address_hash_task, timeout) || case Task.yield(get_implementation_address_hash_task, timeout) ||
Task.ignore(get_implementation_address_hash_task) do Task.ignore(get_implementation_address_hash_task) do
{:ok, :empty} -> {:ok, :empty} ->
{[], [], nil} nil
{:ok, :error} -> {:ok, :error} ->
format_proxy_implementations_response(proxy_implementations) proxy_implementations
{:ok, %__MODULE__{} = result} -> {:ok, %__MODULE__{} = result} ->
format_proxy_implementations_response(result) result
_ -> _ ->
format_proxy_implementations_response(proxy_implementations) proxy_implementations
end end
else else
format_proxy_implementations_response(proxy_implementations) proxy_implementations
end end
end end
def get_implementation(_, _), do: {[], [], nil} def get_implementation(_, _), do: nil
defp fetch_implementation?(implementation_address_fetched?, refetch_necessity_checked?, implementation_updated_at) do defp fetch_implementation?(implementation_address_fetched?, refetch_necessity_checked?, implementation_updated_at) do
(!implementation_address_fetched? || !refetch_necessity_checked?) && (!implementation_address_fetched? || !refetch_necessity_checked?) &&
check_implementation_refetch_necessity(implementation_updated_at) check_implementation_refetch_necessity(implementation_updated_at)
end end
defp format_proxy_implementations_response(proxy_implementations) do
{(proxy_implementations && db_implementation_data_converter(proxy_implementations.address_hashes)) || [],
(proxy_implementations && db_implementation_data_converter(proxy_implementations.names)) || [],
proxy_implementations && proxy_implementations.proxy_type}
end
@doc """ @doc """
Function checks by timestamp if new implementation fetching needed Function checks by timestamp if new implementation fetching needed
""" """
@ -344,7 +338,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
{implementation_addresses, implementation_names} = {implementation_addresses, implementation_names} =
implementation_address_hash_strings implementation_address_hash_strings
|> Enum.map(fn implementation_address_hash_string -> |> Enum.map(fn implementation_address_hash_string ->
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string), with {:ok, implementation_address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string),
{:implementation, {%SmartContract{name: name}, _}} <- { {:implementation, {%SmartContract{name: name}, _}} <- {
:implementation, :implementation,
SmartContract.address_hash_to_smart_contract_with_bytecode_twin(implementation_address_hash, options) SmartContract.address_hash_to_smart_contract_with_bytecode_twin(implementation_address_hash, options)
@ -415,14 +409,6 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
|> Repo.update() |> Repo.update()
end end
defp db_implementation_data_converter(nil), do: nil
defp db_implementation_data_converter(list) when is_list(list),
do: list |> Enum.map(&db_implementation_data_converter(&1))
defp db_implementation_data_converter(string) when is_binary(string), do: string
defp db_implementation_data_converter(other), do: to_string(other)
@doc """ @doc """
Returns proxy's implementation names Returns proxy's implementation names
""" """
@ -440,4 +426,99 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
end end
def names(_, _), do: [] def names(_, _), do: []
if Application.compile_env(:explorer, :chain_type) == :filecoin do
@doc """
Fetches associated addresses for Filecoin based on the provided nested IDs.
This function is used in Ecto preload to retrieve addresses for proxy implementations.
## Parameters
- nested_ids: A list of nested IDs for which the associated addresses need to be fetched.
## Returns
- A list of associated addresses for the given nested IDs.
"""
def addresses_association_for_filecoin(nested_ids) do
query = from(address in Address, where: address.hash in ^List.flatten(nested_ids))
addresses_map =
query
|> Repo.replica().all()
|> Map.new(&{&1.hash, &1})
for ids <- nested_ids,
address <- ids |> Enum.map(&addresses_map[&1]) do
{ids, address}
end
end
@doc """
Returns the association for proxy implementations.
This function is used to retrieve the proxy_implementations associations for address
## Examples
iex> Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_association()
[proxy_implementations: [addresses: &Explorer.Chain.SmartContract.Proxy.Models.Implementation.addresses_association_for_filecoin/1]]
"""
@spec proxy_implementations_association() :: [
proxy_implementations: [addresses: fun()]
]
def proxy_implementations_association do
[proxy_implementations: proxy_implementations_addresses_association()]
end
@doc """
Returns the association of proxy implementation addresses.
This function is used to retrieve the addresses associations for proxy
## Examples
iex> Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_addresses_association()
[addresses: &Explorer.Chain.SmartContract.Proxy.Models.Implementation.addresses_association_for_filecoin/1]
"""
@spec proxy_implementations_association() :: [addresses: fun()]
def proxy_implementations_addresses_association do
[addresses: &__MODULE__.addresses_association_for_filecoin/1]
end
else
@doc """
Returns the association for proxy implementations.
This function is used to retrieve the proxy_implementations associations for address
## Examples
iex> Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_association()
:proxy_implementations
"""
@spec proxy_implementations_association() :: :proxy_implementations
def proxy_implementations_association do
:proxy_implementations
end
@doc """
Returns the association of proxy implementation addresses.
This function is used to retrieve the addresses associations for proxy.
(Returns [] since in chain types other than Filecoin, the addresses are not needed to preload)
## Examples
iex> Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_addresses_association()
[]
"""
@spec proxy_implementations_addresses_association() :: []
def proxy_implementations_addresses_association do
[]
end
end
end end

@ -136,6 +136,7 @@ defmodule Explorer.Chain.TokenTransfer do
require Explorer.Chain.TokenTransfer.Schema require Explorer.Chain.TokenTransfer.Schema
import Ecto.Changeset import Ecto.Changeset
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{DenormalizationHelper, Hash, Log, TokenTransfer} alias Explorer.Chain.{DenormalizationHelper, Hash, Log, TokenTransfer}
@ -239,8 +240,8 @@ defmodule Explorer.Chain.TokenTransfer do
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:transaction, :transaction,
:token, :token,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]],
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]]
]) ])
only_consensus_transfers_query() only_consensus_transfers_query()
@ -266,8 +267,8 @@ defmodule Explorer.Chain.TokenTransfer do
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:transaction, :transaction,
:token, :token,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]],
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]]
]) ])
only_consensus_transfers_query() only_consensus_transfers_query()
@ -299,8 +300,8 @@ defmodule Explorer.Chain.TokenTransfer do
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:transaction, :transaction,
:token, :token,
[from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]],
[to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]]
]) ])
only_consensus_transfers_query() only_consensus_transfers_query()

@ -90,14 +90,13 @@ defmodule Explorer.Etherscan.Contracts do
def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do
updated_smart_contract = updated_smart_contract =
if Proxy.proxy_contract?(smart_contract) do if Proxy.proxy_contract?(smart_contract) do
implementation = Implementation.get_implementation(smart_contract)
smart_contract smart_contract
|> Map.put(:is_proxy, true) |> Map.put(:is_proxy, true)
|> Map.put( |> Map.put(
:implementation_address_hash_strings, :implementation_address_hash_strings,
smart_contract implementation.address_hashes
|> Implementation.get_implementation()
|> Tuple.to_list()
|> List.first()
) )
else else
smart_contract smart_contract

@ -9,6 +9,7 @@ defmodule Explorer.MicroserviceInterfaces.Metadata do
alias HTTPoison.Response alias HTTPoison.Response
import Explorer.Chain.Address.MetadataPreloader, only: [maybe_preload_meta: 3] import Explorer.Chain.Address.MetadataPreloader, only: [maybe_preload_meta: 3]
import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0]
require Logger require Logger
@request_timeout :timer.seconds(5) @request_timeout :timer.seconds(5)
@ -143,7 +144,11 @@ defmodule Explorer.MicroserviceInterfaces.Metadata do
"items", "items",
addresses addresses
|> Chain.hashes_to_addresses( |> Chain.hashes_to_addresses(
necessity_by_association: %{names: :optional, smart_contract: :optional, proxy_implementations: :optional} necessity_by_association: %{
:names => :optional,
:smart_contract => :optional,
proxy_implementations_association() => :optional
}
) )
|> Enum.map(fn address -> {address, address.transactions_count} end) |> Enum.map(fn address -> {address, address.transactions_count} end)
)} )}

@ -242,9 +242,9 @@ defmodule Explorer.SmartContract.Helper do
@doc """ @doc """
Pre-fetches implementation for unverified smart contract or verified proxy smart-contract Pre-fetches implementation for unverified smart contract or verified proxy smart-contract
""" """
@spec pre_fetch_implementations(Address.t()) :: {any(), atom() | nil} @spec pre_fetch_implementations(Address.t()) :: Implementation.t() | nil
def pre_fetch_implementations(address) do def pre_fetch_implementations(address) do
{implementation_address_hashes, implementation_names, proxy_type} = implementation =
with {:verified_smart_contract, %SmartContract{}} <- {:verified_smart_contract, address.smart_contract}, with {:verified_smart_contract, %SmartContract{}} <- {:verified_smart_contract, address.smart_contract},
{:proxy?, true} <- {:proxy?, address_is_proxy?(address, @api_true)} do {:proxy?, true} <- {:proxy?, address_is_proxy?(address, @api_true)} do
Implementation.get_implementation(address.smart_contract, @api_true) Implementation.get_implementation(address.smart_contract, @api_true)
@ -256,17 +256,14 @@ defmodule Explorer.SmartContract.Helper do
} }
Implementation.get_implementation(smart_contract, @api_true) Implementation.get_implementation(smart_contract, @api_true)
else
{[], [], nil}
end end
{:proxy?, false} -> {:proxy?, false} ->
{[], [], nil} nil
end end
implementations = Proxy.proxy_object_info(implementation_address_hashes, implementation_names) implementation
|> Chain.select_repo(@api_true).preload(Implementation.proxy_implementations_addresses_association())
{implementations, proxy_type}
end end
@doc """ @doc """

@ -28,7 +28,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# fetch nil implementation and don't save it to db # fetch nil implementation and don't save it to db
TestHelper.get_eip1967_implementation_zero_addresses() TestHelper.get_eip1967_implementation_zero_addresses()
assert {[], [], nil} = Implementation.get_implementation(smart_contract) assert is_nil(Implementation.get_implementation(smart_contract))
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash) assert_empty_implementation(smart_contract.address_hash)
@ -43,9 +43,13 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect_address_in_oz_slot_response(string_implementation_address_hash) expect_address_in_oz_slot_response(string_implementation_address_hash)
implementation_address_hash = implementation_smart_contract.address_hash
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} = assert %Implementation{
Implementation.get_implementation(smart_contract) address_hashes: [^implementation_address_hash],
names: ["implementation"],
proxy_type: :eip1967
} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
@ -57,8 +61,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_error_response() TestHelper.get_eip1967_implementation_error_response()
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} = assert %Implementation{
Implementation.get_implementation(smart_contract) address_hashes: [^implementation_address_hash],
names: ["implementation"],
proxy_type: :eip1967
} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
@ -78,8 +85,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy) Application.put_env(:explorer, :proxy, proxy)
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} = assert %Implementation{
Implementation.get_implementation(smart_contract) address_hashes: [^implementation_address_hash],
names: ["implementation"],
proxy_type: :eip1967
} = Implementation.get_implementation(smart_contract)
{contract_2, _} = SmartContract.address_hash_to_smart_contract_with_bytecode_twin(smart_contract.address_hash) {contract_2, _} = SmartContract.address_hash_to_smart_contract_with_bytecode_twin(smart_contract.address_hash)
implementation_2 = Implementation.get_proxy_implementations(smart_contract.address_hash) implementation_2 = Implementation.get_proxy_implementations(smart_contract.address_hash)
@ -96,7 +106,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_zero_addresses() TestHelper.get_eip1967_implementation_zero_addresses()
assert {[], [], nil} = Implementation.get_implementation(smart_contract) assert is_nil(Implementation.get_implementation(smart_contract))
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
@ -113,7 +123,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
test "get_implementation/1 for twins contract" do test "get_implementation/1 for twins contract" do
# return nils for nil # return nils for nil
assert {[], [], nil} = Implementation.get_implementation(nil) assert is_nil(Implementation.get_implementation(nil))
smart_contract = insert(:smart_contract) smart_contract = insert(:smart_contract)
twin_address = insert(:contract_address) twin_address = insert(:contract_address)
@ -130,11 +140,15 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy) Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation # fetch nil implementation
assert {[], [], :unknown} = Implementation.get_implementation(bytecode_twin) assert %Implementation{address_hashes: [], names: [], proxy_type: :unknown} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash) refute_implementations(smart_contract.address_hash)
assert {[], [], :unknown} = Implementation.get_implementation(bytecode_twin) assert %Implementation{address_hashes: [], names: [], proxy_type: :unknown} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash) refute_implementations(smart_contract.address_hash)
@ -149,8 +163,13 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect_address_in_oz_slot_response(string_implementation_address_hash) expect_address_in_oz_slot_response(string_implementation_address_hash)
implementation_address_hash = implementation_smart_contract.address_hash
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} = assert %Implementation{
address_hashes: [^implementation_address_hash],
names: ["implementation"],
proxy_type: :eip1967
} =
Implementation.get_implementation(bytecode_twin) Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
@ -165,7 +184,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
refute_implementations(smart_contract.address_hash) refute_implementations(smart_contract.address_hash)
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} = assert %Implementation{
address_hashes: [^implementation_address_hash],
names: ["implementation"],
proxy_type: :eip1967
} =
Implementation.get_implementation(bytecode_twin) Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
@ -178,11 +201,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
_implementation_smart_contract = insert(:smart_contract, name: "implementation") _implementation_smart_contract = insert(:smart_contract, name: "implementation")
# fetch nil implementation # fetch nil implementation
assert {[], [], nil} = Implementation.get_implementation(bytecode_twin) assert is_nil(Implementation.get_implementation(bytecode_twin))
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash) refute_implementations(smart_contract.address_hash)
assert {[], [], nil} = Implementation.get_implementation(bytecode_twin) assert is_nil(Implementation.get_implementation(bytecode_twin))
verify!(EthereumJSONRPC.Mox) verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash) refute_implementations(smart_contract.address_hash)

@ -2597,7 +2597,7 @@ defmodule Explorer.ChainTest do
:contracts_creation_transaction, :contracts_creation_transaction,
:token, :token,
[smart_contract: :smart_contract_additional_sources], [smart_contract: :smart_contract_additional_sources],
:proxy_implementations Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_association()
]) ])
options = [ options = [

Loading…
Cancel
Save