Merge pull request #4331 from blockscout/np-add-partial-sourcify

Add support for partially verified via sourcify contracts
pull/4355/head
Victor Baranov 3 years ago committed by GitHub
commit 73918897d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .dialyzer-ignore
  2. 1
      CHANGELOG.md
  3. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  4. 61
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  5. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex
  6. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex
  7. 11
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  8. 41
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  9. 5
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  10. 2308
      apps/block_scout_web/priv/gettext/default.pot
  11. 2308
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  12. 2
      apps/explorer/config/config.exs
  13. 111
      apps/explorer/lib/explorer/chain.ex
  14. 11
      apps/explorer/lib/explorer/chain/smart_contract.ex
  15. 7
      apps/explorer/lib/explorer/smart_contract/publisher.ex
  16. 28
      apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
  17. 9
      apps/explorer/priv/repo/migrations/20210701084814_support_partial_match.exs
  18. 161
      apps/explorer/test/explorer/chain_test.exs

@ -23,8 +23,9 @@ lib/indexer/fetcher/token_total_supply_on_demand.ex:16
lib/explorer/exchange_rates/source.ex:110 lib/explorer/exchange_rates/source.ex:110
lib/explorer/exchange_rates/source.ex:113 lib/explorer/exchange_rates/source.ex:113
lib/explorer/smart_contract/verifier.ex:89 lib/explorer/smart_contract/verifier.ex:89
lib/block_scout_web/templates/address_contract/index.html.eex:162 lib/block_scout_web/templates/address_contract/index.html.eex:150
lib/block_scout_web/templates/address_contract/index.html.eex:193
lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no local return lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no local return
lib/explorer/staking/stake_snapshotting.ex:147 lib/explorer/staking/stake_snapshotting.ex:147
lib/explorer/third_party_integrations/sourcify.ex:65 lib/explorer/third_party_integrations/sourcify.ex:70
lib/explorer/third_party_integrations/sourcify.ex:68 lib/explorer/third_party_integrations/sourcify.ex:73

@ -1,6 +1,7 @@
## Current ## Current
### Features ### Features
- [#4331](https://github.com/blockscout/blockscout/pull/4331) - Added support for partially verified contracts via [Sourcify](https://sourcify.dev)
- [#4323](https://github.com/blockscout/blockscout/pull/4323) - Renamed Contract Byte Code, add Contract Creation Code on contract's page - [#4323](https://github.com/blockscout/blockscout/pull/4323) - Renamed Contract Byte Code, add Contract Creation Code on contract's page
- [#4312](https://github.com/blockscout/blockscout/pull/4312) - Display pending transactions on address page - [#4312](https://github.com/blockscout/blockscout/pull/4312) - Display pending transactions on address page
- [#4299](https://github.com/blockscout/blockscout/pull/4299) - Added sourcify verification api endpoint - [#4299](https://github.com/blockscout/blockscout/pull/4299) - Added sourcify verification api endpoint

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.AddressContractController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.AccessHelpers alias BlockScoutWeb.AccessHelpers
alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
@ -19,8 +20,9 @@ defmodule BlockScoutWeb.AddressContractController do
] ]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true), {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do _ <- VerificationController.check_and_verify(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render( render(
conn, conn,
"index.html", "index.html",

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
alias Explorer.ThirdPartyIntegrations.Sourcify alias Explorer.ThirdPartyIntegrations.Sourcify
def new(conn, %{"address_id" => address_hash_string}) do def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_verified?(address_hash_string) do if Chain.smart_contract_fully_verified?(address_hash_string) do
redirect(conn, to: address_path(conn, :show, address_hash_string)) redirect(conn, to: address_path(conn, :show, address_hash_string))
else else
changeset = changeset =
@ -65,7 +65,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
json_file = json_files |> Enum.at(0) json_file = json_files |> Enum.at(0)
if json_file do if json_file do
if Chain.smart_contract_verified?(address_hash_string) do if Chain.smart_contract_fully_verified?(address_hash_string) do
EventsPublisher.broadcast( EventsPublisher.broadcast(
prepare_verification_error( prepare_verification_error(
"This contract already verified in Blockscout.", "This contract already verified in Blockscout.",
@ -125,25 +125,39 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
end end
end end
def get_metadata_and_publish(address_hash_string, nil) do
case Sourcify.get_metadata(address_hash_string) do
{:ok, verification_metadata} ->
proccess_metadata_add_publish(address_hash_string, verification_metadata, false)
{:error, %{"error" => error}} ->
{:error, error: error}
end
end
def get_metadata_and_publish(address_hash_string, conn) do def get_metadata_and_publish(address_hash_string, conn) do
case Sourcify.get_metadata(address_hash_string) do case Sourcify.get_metadata(address_hash_string) do
{:ok, verification_metadata} -> {:ok, verification_metadata} ->
proccess_metadata_add_publish(address_hash_string, verification_metadata, false, conn)
{:error, %{"error" => error}} ->
EventsPublisher.broadcast(
prepare_verification_error(error, address_hash_string, conn),
:on_demand
)
end
end
defp proccess_metadata_add_publish(address_hash_string, verification_metadata, is_partial, conn \\ nil) do
%{"params_to_publish" => params_to_publish, "abi" => abi, "secondary_sources" => secondary_sources} = %{"params_to_publish" => params_to_publish, "abi" => abi, "secondary_sources" => secondary_sources} =
parse_params_from_sourcify(address_hash_string, verification_metadata) parse_params_from_sourcify(address_hash_string, verification_metadata)
ContractController.publish(conn, %{ ContractController.publish(conn, %{
"addressHash" => address_hash_string, "addressHash" => address_hash_string,
"params" => params_to_publish, "params" => Map.put(params_to_publish, "partially_verified", is_partial),
"abi" => abi, "abi" => abi,
"secondarySources" => secondary_sources "secondarySources" => secondary_sources
}) })
{:error, %{"error" => error}} ->
EventsPublisher.broadcast(
prepare_verification_error(error, address_hash_string, conn),
:on_demand
)
end
end end
def prepare_files_array(files) do def prepare_files_array(files) do
@ -256,4 +270,31 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
_ -> 200 _ -> 200
end end
end end
def check_and_verify(address_hash_string) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
{:ok, :already_fully_verified}
else
if Chain.smart_contract_verified?(address_hash_string) do
case Sourcify.check_by_address(address_hash_string) do
{:ok, _verified_status} ->
get_metadata_and_publish(address_hash_string, nil)
_ ->
{:error, :not_verified}
end
else
case Sourcify.check_by_address_any(address_hash_string) do
{:ok, "full", metadata} ->
proccess_metadata_add_publish(address_hash_string, metadata, false)
{:ok, "partial", metadata} ->
proccess_metadata_add_publish(address_hash_string, metadata, true)
_ ->
{:error, :not_verified}
end
end
end
end
end end

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do
alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion}
def new(conn, %{"address_id" => address_hash_string}) do def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_verified?(address_hash_string) do if Chain.smart_contract_fully_verified?(address_hash_string) do
redirect(conn, to: address_path(conn, :show, address_hash_string)) redirect(conn, to: address_path(conn, :show, address_hash_string))
else else
changeset = changeset =

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do
alias Explorer.ThirdPartyIntegrations.Sourcify alias Explorer.ThirdPartyIntegrations.Sourcify
def new(conn, %{"address_id" => address_hash_string}) do def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_verified?(address_hash_string) do if Chain.smart_contract_fully_verified?(address_hash_string) do
redirect(conn, to: address_path(conn, :show, address_hash_string)) redirect(conn, to: address_path(conn, :show, address_hash_string))
else else
case Sourcify.check_by_address(address_hash_string) do case Sourcify.check_by_address(address_hash_string) do

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
alias BlockScoutWeb.API.RPC.Helpers alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.Chain.SmartContract alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.SmartContract.Publisher alias Explorer.SmartContract.Publisher
alias Explorer.ThirdPartyIntegrations.Sourcify alias Explorer.ThirdPartyIntegrations.Sourcify
@ -53,7 +53,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
[] []
end end
if Chain.smart_contract_verified?(address_hash) do if Chain.smart_contract_fully_verified?(address_hash) do
render(conn, :error, error: "Smart-contract already verified.") render(conn, :error, error: "Smart-contract already verified.")
else else
case Sourcify.check_by_address(address_hash) do case Sourcify.check_by_address(address_hash) do
@ -214,6 +214,10 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end end
end end
def publish(nil, %{"addressHash" => _address_hash} = input) do
publish_without_broadcast(input)
end
def publish(conn, %{"addressHash" => address_hash} = input) do def publish(conn, %{"addressHash" => address_hash} = input) do
result = publish_without_broadcast(input) result = publish_without_broadcast(input)
@ -261,6 +265,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
def getsourcecode(conn, params) do def getsourcecode(conn, params) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param) do {:format, {:ok, address_hash}} <- to_address_hash(address_param) do
_ = VerificationController.check_and_verify(address_param)
address = Chain.address_hash_to_address_with_source_code(address_hash) address = Chain.address_hash_to_address_with_source_code(address_hash)
render(conn, :getsourcecode, %{ render(conn, :getsourcecode, %{
@ -364,6 +369,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end end
defp to_smart_contract(address_hash) do defp to_smart_contract(address_hash) do
_ = VerificationController.check_and_verify(Hash.to_string(address_hash))
result = result =
case Chain.address_hash_to_smart_contract(address_hash) do case Chain.address_hash_to_smart_contract(address_hash) do
nil -> nil ->

@ -3,8 +3,8 @@
<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %> <% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> <% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
<% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %> <% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %>
<% fully_verified = Chain.smart_contract_fully_verified?(@address.hash)%>
<% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %> <% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %>
<section class="container"> <section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> <% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
@ -28,13 +28,21 @@
<% end %> <% end %>
<%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %> <%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %> <% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %>
<%= if @address.smart_contract.partially_verified && smart_contract_verified do %>
<div class="mb-4">
<i style="color: #f7b32b;" class="fa fa-info-circle"></i><span> <%= gettext("This contract has been partially verified via Sourcify.") %>
<% else %>
<%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<div class="mb-4"> <div class="mb-4">
<i style="color: #f7b32b;" class="fa fa-info-circle"></i><span> <%= gettext("This contract has been verified via Sourcify.") %> <i style="color: #f7b32b;" class="fa fa-info-circle"></i><span> <%= gettext("This contract has been verified via Sourcify.") %>
<a data-test="external_url" href=<%= sourcify_repo_url(@address.hash) %> target="_blank"> <% end %>
<% end %>
<%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<a data-test="external_url" href=<%= sourcify_repo_url(@address.hash, @address.smart_contract.partially_verified) %> target="_blank">
View contract in Sourcify repository <span class="external-token-icon"><%= render BlockScoutWeb.IconsView, "_external_link.html" %></span> View contract in Sourcify repository <span class="external-token-icon"><%= render BlockScoutWeb.IconsView, "_external_link.html" %></span>
</a> </a>
</div> </div>
<% end %> <% end %>
<div class="mb-4"> <div class="mb-4">
<dl class="row"> <dl class="row">
@ -135,15 +143,38 @@
<%= if creation_code(@address) do %> <%= if creation_code(@address) do %>
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract Creation Code" %></h3> <h3><%= gettext "Contract Creation Code" %></h3>
<div class="buttons">
<button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= creation_code(@address) %>" aria-label="copy contract creation code"> <button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= creation_code(@address) %>" aria-label="copy contract creation code">
<%= gettext "Copy Contract Creation Code" %> <%= gettext "Copy Contract Creation Code" %>
</button> </button>
<%= if match?({:selfdestructed, _}, contract_creation_code) do %>
<div class="button button-disabled button-sm float-right ml-3">
<%= gettext("Verify & Publish") %>
</div>
<% else %>
<%= if !fully_verified do %>
<% path =
if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do
address_verify_contract_path(@conn, :new, @address.hash)
else
address_verify_contract_via_flattened_code_path(@conn, :new, @address.hash)
end
%>
<%= link(
gettext("Verify & Publish"),
to: path,
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
<% end %>
</div>
</div> </div>
<div class="tile tile-muted mb-4"> <div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= creation_code(@address) %></code></pre> <pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= creation_code(@address) %></code></pre>
</div> </div>
<% end %> <% end %>
<%= if smart_contract_verified do %> <%= if fully_verified do %>
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Deployed ByteCode" %></h3> <h3><%= gettext "Deployed ByteCode" %></h3>
<button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code"> <button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code">
@ -159,12 +190,12 @@
<button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code"> <button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code">
<%= gettext "Copy Deployed ByteCode" %> <%= gettext "Copy Deployed ByteCode" %>
</button> </button>
<%= if match?({:selfdestructed, _}, contract_creation_code) do %> <%= if match?({:selfdestructed, _}, contract_creation_code) and !creation_code(@address) do %>
<div class="button button-disabled button-sm float-right ml-3"> <div class="button button-disabled button-sm float-right ml-3">
<%= gettext("Verify & Publish") %> <%= gettext("Verify & Publish") %>
</div> </div>
<% else %> <% else %>
<%= if !smart_contract_verified do %> <%= if !fully_verified and !creation_code(@address) do %>
<% path = <% path =
if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do
address_verify_contract_path(@conn, :new, @address.hash) address_verify_contract_path(@conn, :new, @address.hash)

@ -127,10 +127,11 @@ defmodule BlockScoutWeb.AddressContractView do
address.contracts_creation_transaction.input address.contracts_creation_transaction.input
end end
def sourcify_repo_url(address_hash) do def sourcify_repo_url(address_hash, partial_match) do
checksummed_hash = Address.checksum(address_hash) checksummed_hash = Address.checksum(address_hash)
chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id] chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id]
repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url] repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url]
repo_url <> chain_id <> "/" <> checksummed_hash <> "/" match = if partial_match, do: "/partial_match/", else: "/full_match/"
repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/"
end end
end end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -245,7 +245,7 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify,
server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server", server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server",
enabled: System.get_env("ENABLE_SOURCIFY_INTEGRATION") == "true", enabled: System.get_env("ENABLE_SOURCIFY_INTEGRATION") == "true",
chain_id: System.get_env("CHAIN_ID"), chain_id: System.get_env("CHAIN_ID"),
repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts/full_match/" repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts"
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

@ -3479,6 +3479,79 @@ defmodule Explorer.Chain do
end end
end end
@doc """
Updates a `t:SmartContract.t/0`.
Has the similar logic as create_smart_contract/1.
Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing
status `partially verified` to `fully verified` (re-verify).
"""
@spec update_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
address_hash = Map.get(attrs, :address_hash)
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
query_sources =
from(
source in SmartContractAdditionalSource,
where: source.address_hash == ^address_hash
)
_delete_sources = Repo.delete_all(query_sources)
smart_contract = Repo.one(query)
smart_contract_changeset =
smart_contract
|> SmartContract.changeset(attrs)
|> Changeset.put_change(:external_libraries, external_libraries)
new_contract_additional_source = %SmartContractAdditionalSource{}
smart_contract_additional_sources_changesets =
if secondary_sources do
secondary_sources
|> Enum.map(fn changeset ->
new_contract_additional_source
|> SmartContractAdditionalSource.changeset(changeset)
end)
else
[]
end
# Enforce ShareLocks tables order (see docs: sharelocks.md)
insert_contract_query =
Multi.new()
|> Multi.update(:smart_contract, smart_contract_changeset)
insert_contract_query_with_additional_sources =
smart_contract_additional_sources_changesets
|> Enum.with_index()
|> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
end)
insert_result =
insert_contract_query_with_additional_sources
|> Repo.transaction()
case insert_result do
{:ok, %{smart_contract: smart_contract}} ->
{:ok, smart_contract}
{:error, :smart_contract, changeset, _} ->
{:error, changeset}
{:error, :set_address_verified, message, _} ->
{:error, message}
end
end
defp set_address_verified(repo, address_hash) do defp set_address_verified(repo, address_hash) do
query = query =
from( from(
@ -3712,22 +3785,56 @@ defmodule Explorer.Chain do
end end
end end
def smart_contract_verified?(address_hash_str) do def smart_contract_fully_verified?(address_hash_str) when is_binary(address_hash_str) do
case string_to_address_hash(address_hash_str) do case string_to_address_hash(address_hash_str) do
{:ok, address_hash} -> {:ok, address_hash} ->
check_fully_verified(address_hash)
_ ->
false
end
end
def smart_contract_fully_verified?(address_hash) do
check_fully_verified(address_hash)
end
defp check_fully_verified(address_hash) do
query = query =
from( from(
smart_contract in SmartContract, smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash where: smart_contract.address_hash == ^address_hash
) )
if Repo.one(query), do: true, else: false result = Repo.one(query)
if result, do: !result.partially_verified
end
def smart_contract_verified?(address_hash_str) when is_binary(address_hash_str) do
case string_to_address_hash(address_hash_str) do
{:ok, address_hash} ->
check_verified(address_hash)
_ -> _ ->
false false
end end
end end
def smart_contract_verified?(address_hash) do
check_verified(address_hash)
end
defp check_verified(address_hash) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
if Repo.one(query), do: true, else: false
end
defp fetch_transactions(paging_options \\ nil) do defp fetch_transactions(paging_options \\ nil) do
Transaction Transaction
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index) |> order_by([transaction], desc: transaction.block_number, desc: transaction.index)

@ -193,6 +193,7 @@ defmodule Explorer.Chain.SmartContract do
* `abi` - The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for this * `abi` - The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for this
contract. contract.
* `verified_via_sourcify` - whether contract verified through Sourcify utility or not. * `verified_via_sourcify` - whether contract verified through Sourcify utility or not.
* `partially_verified` - whether contract verified using partial matched source code or not.
""" """
@type t :: %Explorer.Chain.SmartContract{ @type t :: %Explorer.Chain.SmartContract{
@ -204,7 +205,8 @@ defmodule Explorer.Chain.SmartContract do
evm_version: String.t() | nil, evm_version: String.t() | nil,
optimization_runs: non_neg_integer() | nil, optimization_runs: non_neg_integer() | nil,
abi: [function_description], abi: [function_description],
verified_via_sourcify: boolean | nil verified_via_sourcify: boolean | nil,
partially_verified: boolean | nil
} }
schema "smart_contracts" do schema "smart_contracts" do
@ -218,6 +220,7 @@ defmodule Explorer.Chain.SmartContract do
embeds_many(:external_libraries, ExternalLibrary) embeds_many(:external_libraries, ExternalLibrary)
field(:abi, {:array, :map}) field(:abi, {:array, :map})
field(:verified_via_sourcify, :boolean) field(:verified_via_sourcify, :boolean)
field(:partially_verified, :boolean)
has_many( has_many(
:decompiled_smart_contracts, :decompiled_smart_contracts,
@ -252,7 +255,8 @@ defmodule Explorer.Chain.SmartContract do
:constructor_arguments, :constructor_arguments,
:evm_version, :evm_version,
:optimization_runs, :optimization_runs,
:verified_via_sourcify :verified_via_sourcify,
:partially_verified
]) ])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash]) |> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash) |> unique_constraint(:address_hash)
@ -271,7 +275,8 @@ defmodule Explorer.Chain.SmartContract do
:evm_version, :evm_version,
:optimization_runs, :optimization_runs,
:constructor_arguments, :constructor_arguments,
:verified_via_sourcify :verified_via_sourcify,
:partially_verified
]) ])
|> validate_required([:name, :compiler_version, :optimization, :address_hash]) |> validate_required([:name, :compiler_version, :optimization, :address_hash])

@ -48,8 +48,12 @@ defmodule Explorer.SmartContract.Publisher do
def publish_smart_contract(address_hash, params, abi) do def publish_smart_contract(address_hash, params, abi) do
attrs = address_hash |> attributes(params, abi) attrs = address_hash |> attributes(params, abi)
if Chain.smart_contract_verified?(address_hash) do
Chain.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
else
Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end end
end
defp unverified_smart_contract(address_hash, params, error, error_message) do defp unverified_smart_contract(address_hash, params, error, error_message) do
attrs = attributes(address_hash, params) attrs = attributes(address_hash, params)
@ -91,7 +95,8 @@ defmodule Explorer.SmartContract.Publisher do
external_libraries: prepared_external_libraries, external_libraries: prepared_external_libraries,
secondary_sources: params["secondary_sources"], secondary_sources: params["secondary_sources"],
abi: abi, abi: abi,
verified_via_sourcify: params["verified_via_sourcify"] verified_via_sourcify: params["verified_via_sourcify"],
partially_verified: params["partially_verified"]
} }
end end

@ -13,6 +13,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
http_get_request(check_by_address_url(), params) http_get_request(check_by_address_url(), params)
end end
def check_by_address_any(address_hash_string) do
get_metadata_full_url = get_metadata_any_url() <> "/" <> address_hash_string
http_get_request(get_metadata_full_url, [])
end
def get_metadata(address_hash_string) do def get_metadata(address_hash_string) do
get_metadata_full_url = get_metadata_url() <> "/" <> address_hash_string get_metadata_full_url = get_metadata_url() <> "/" <> address_hash_string
http_get_request(get_metadata_full_url, []) http_get_request(get_metadata_full_url, [])
@ -90,6 +95,9 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
url =~ "/verify" -> url =~ "/verify" ->
parse_verify_http_response(body) parse_verify_http_response(body)
url =~ "/files/any" ->
parse_get_metadata_any_http_response(body)
url =~ "/files/" -> url =~ "/files/" ->
parse_get_metadata_http_response(body) parse_get_metadata_http_response(body)
@ -143,6 +151,21 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
end end
defp parse_get_metadata_any_http_response(body) do
body_json = decode_json(body)
case body_json do
%{"message" => message, "errors" => errors} ->
{:error, "#{message}: #{decode_json(errors)}"}
%{"status" => status, "files" => metadata} ->
{:ok, status, metadata}
_ ->
{:error, "Unknown Error"}
end
end
defp parse_http_error_response(body) do defp parse_http_error_response(body) do
body_json = decode_json(body) body_json = decode_json(body)
@ -181,4 +204,9 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
chain_id = config(:chain_id) chain_id = config(:chain_id)
"#{base_server_url()}" <> "/files/" <> chain_id "#{base_server_url()}" <> "/files/" <> chain_id
end end
defp get_metadata_any_url do
chain_id = config(:chain_id)
"#{base_server_url()}" <> "/files/any/" <> chain_id
end
end end

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.SupportPartialMatch do
use Ecto.Migration
def change do
alter table(:smart_contracts) do
add(:partially_verified, :boolean, null: true)
end
end
end

@ -3715,6 +3715,167 @@ defmodule Explorer.ChainTest do
end end
end end
describe "update_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
partially_verified: true
}
secondary_sources = [
%{
file_name: "storage.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_1.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
changed_sources = [
%{
file_name: "storage_2.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_3.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
_ = Chain.create_smart_contract(valid_attrs, [], secondary_sources)
{:ok,
valid_attrs: valid_attrs,
address: created_contract_address,
secondary_sources: secondary_sources,
changed_sources: changed_sources}
end
test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{} = smart_contract} =
Chain.update_smart_contract(%{address_hash: address.hash, partially_verified: false})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == false
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
end
test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{} = smart_contract} = Chain.update_smart_contract(%{address_hash: address.hash})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
end
test "check additional sources update", %{
address: address,
secondary_sources: secondary_sources,
changed_sources: changed_sources
} do
sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_before_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(secondary_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
assert {:ok, %SmartContract{} = smart_contract} =
Chain.update_smart_contract(%{address_hash: address.hash}, [], changed_sources)
sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_after_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(changed_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
end
end
describe "stream_unfetched_balances/2" do describe "stream_unfetched_balances/2" do
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <> test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do "does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do

Loading…
Cancel
Save