Add vyper multi-part, evm_version for vyper, index of chosen contract for sourcify

pull/6744/head
Nikita Pozdniakov 2 years ago
parent 4a7211e5d1
commit 38f1ab5ceb
No known key found for this signature in database
GPG Key ID: F344106F9804FE5F
  1. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  2. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  3. 41
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
  4. 1
      apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
  5. 16
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs
  6. 2
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
  7. 15
      apps/explorer/lib/explorer/smart_contract/solc_downloader.ex
  8. 4
      apps/explorer/lib/explorer/smart_contract/solidity/publish_helper.ex
  9. 4
      apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex
  10. 45
      apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
  11. 15
      apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex
  12. 33
      apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex
  13. 25
      apps/explorer/lib/explorer/third_party_integrations/sourcify.ex

@ -137,7 +137,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
PublishHelper.get_metadata_and_publish(address_hash_string, conn) PublishHelper.get_metadata_and_publish(address_hash_string, conn)
_ -> _ ->
PublishHelper.verify_and_publish(address_hash_string, files_array, conn) PublishHelper.verify_and_publish(address_hash_string, files_array, conn, false)
end end
end end
else else

@ -255,7 +255,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end end
defp verify_and_publish(address_hash_string, files_array, conn) do defp verify_and_publish(address_hash_string, files_array, conn) do
case Sourcify.verify(address_hash_string, files_array) do case Sourcify.verify(address_hash_string, files_array, nil) do
{:ok, _verified_status} -> {:ok, _verified_status} ->
case Sourcify.check_by_address(address_hash_string) do case Sourcify.check_by_address(address_hash_string) do
{:ok, _verified_status} -> {:ok, _verified_status} ->

@ -25,13 +25,15 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
else: &1 else: &1
)).() )).()
|> (&if(RustVerifierInterface.enabled?(), do: ["multi_part" | &1], else: &1)).() |> (&if(RustVerifierInterface.enabled?(), do: ["multi_part" | &1], else: &1)).()
|> (&if(RustVerifierInterface.enabled?(), do: ["vyper_multi_part" | &1], else: &1)).()
conn conn
|> json(%{ |> json(%{
evm_versions: evm_versions, solidity_evm_versions: evm_versions,
solidity_compiler_versions: solidity_compiler_versions, solidity_compiler_versions: solidity_compiler_versions,
vyper_compiler_versions: vyper_compiler_versions, vyper_compiler_versions: vyper_compiler_versions,
verification_options: verification_options verification_options: verification_options,
vyper_evm_versions: ["byzantium", "constantinople", "petersburg", "istanbul"]
}) })
end end
@ -107,7 +109,9 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
{:no_json_file, %Plug.Upload{path: _path}} <- {:no_json_file, %Plug.Upload{path: _path}} <-
{:no_json_file, PublishHelper.get_one_json(files_array)}, {:no_json_file, PublishHelper.get_one_json(files_array)},
files_content <- PublishHelper.read_files(files_array) do files_content <- PublishHelper.read_files(files_array) do
Que.add(SolidityPublisherWorker, {"sourcify_api_v2", address_hash_string, files_content, conn}) chosen_contract = params["chosen_contract_index"]
Que.add(SolidityPublisherWorker, {"sourcify_api_v2", address_hash_string, files_content, conn, chosen_contract})
conn conn
|> put_view(ApiView) |> put_view(ApiView)
@ -167,6 +171,8 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
} }
|> Map.put("constructor_arguments", Map.get(params, "constructor_args", "") || "") |> Map.put("constructor_arguments", Map.get(params, "constructor_args", "") || "")
|> Map.put("name", Map.get(params, "contract_name", "Vyper_contract")) |> Map.put("name", Map.get(params, "contract_name", "Vyper_contract"))
# |> Map.put("optimization", Map.get(params, "is_optimization_enabled", false))
|> Map.put("evm_version", Map.get(params, "evm_version", "istanbul"))
Que.add(VyperPublisherWorker, {address_hash_string, verification_params}) Que.add(VyperPublisherWorker, {address_hash_string, verification_params})
@ -175,4 +181,33 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
|> render(:message, %{message: "Verification started"}) |> render(:message, %{message: "Verification started"})
end end
end end
def verification_via_vyper_multipart(
conn,
%{"address_hash" => address_hash_string, "compiler_version" => compiler_version, "files" => files} = params
) do
with {:not_found, true} <- {:not_found, RustVerifierInterface.enabled?()},
{:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:already_verified, false} <- {:already_verified, Chain.smart_contract_fully_verified?(address_hash)} do
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
"compiler_version" => compiler_version
}
# |> Map.put("optimization", Map.get(params, "is_optimization_enabled", false))
|> Map.put("evm_version", Map.get(params, "evm_version", "istanbul"))
files_array =
files
|> Map.values()
|> PublishHelper.read_files()
Que.add(VyperPublisherWorker, {address_hash_string, verification_params, files_array})
conn
|> put_view(ApiView)
|> render(:message, %{message: "Verification started"})
end
end
end end

@ -36,6 +36,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do
post("/sourcify", V2.VerificationController, :verification_via_sourcify) post("/sourcify", V2.VerificationController, :verification_via_sourcify)
post("/multi-part", V2.VerificationController, :verification_via_multi_part) post("/multi-part", V2.VerificationController, :verification_via_multi_part)
post("/vyper-code", V2.VerificationController, :verification_via_vyper_code) post("/vyper-code", V2.VerificationController, :verification_via_vyper_code)
post("/vyper-multi-part", V2.VerificationController, :verification_via_vyper_multipart)
end end
end end
end end

@ -13,10 +13,11 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
assert response = json_response(request, 200) assert response = json_response(request, 200)
assert is_list(response["evm_versions"]) assert is_list(response["solidity_evm_versions"])
assert is_list(response["solidity_compiler_versions"]) assert is_list(response["solidity_compiler_versions"])
assert is_list(response["vyper_compiler_versions"]) assert is_list(response["vyper_compiler_versions"])
assert is_list(response["verification_options"]) assert is_list(response["verification_options"])
assert is_list(response["vyper_evm_versions"])
end end
end end
@ -226,7 +227,7 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
end end
describe "/api/v2/smart-contracts/{address_hash}/verification/via/multi-part" do describe "/api/v2/smart-contracts/{address_hash}/verification/via/multi-part" do
test "get 200 for verified contract", %{conn: conn} do test "get 404", %{conn: conn} do
contract = insert(:smart_contract) contract = insert(:smart_contract)
params = %{"compiler_version" => "", "files" => ""} params = %{"compiler_version" => "", "files" => ""}
@ -297,6 +298,17 @@ defmodule BlockScoutWeb.API.V2.VerificationControllerTest do
end end
end end
describe "/api/v2/smart-contracts/{address_hash}/verification/via/vyper-multi-part" do
test "get 404", %{conn: conn} do
contract = insert(:smart_contract)
params = %{"compiler_version" => "", "files" => ""}
request = post(conn, "/api/v2/smart-contracts/#{contract.address_hash}/verification/via/vyper-multi-part", params)
assert %{"message" => "Not found"} = json_response(request, 404)
end
end
defp to_str(list) when is_list(list) do defp to_str(list) when is_list(list) do
Enum.reduce(list, "", fn x, acc -> acc <> to_str(x) end) Enum.reduce(list, "", fn x, acc -> acc <> to_str(x) end)
end end

@ -1,8 +1,6 @@
defmodule BlockScoutWeb.Tokens.SmartContractViewTest do defmodule BlockScoutWeb.Tokens.SmartContractViewTest do
use BlockScoutWeb.ConnCase, async: true use BlockScoutWeb.ConnCase, async: true
@max_size Enum.at(Tuple.to_list(Application.get_env(:block_scout_web, :max_size_to_show_array_as_is)), 0)
alias BlockScoutWeb.SmartContractView alias BlockScoutWeb.SmartContractView
describe "queryable?" do describe "queryable?" do

@ -9,22 +9,14 @@ defmodule Explorer.SmartContract.SolcDownloader do
@latest_compiler_refetch_time :timer.minutes(30) @latest_compiler_refetch_time :timer.minutes(30)
defp debug(value, key) do
require Logger
Logger.configure(truncate: :infinity)
Logger.info(key)
Logger.info(Kernel.inspect(value, limit: :infinity, printable_limit: :infinity))
value
end
def ensure_exists(version) do def ensure_exists(version) do
path = file_path(version) |> debug("filepath") path = file_path(version)
if File.exists?(path) && version !== "latest" do if File.exists?(path) && version !== "latest" do
path |> debug("first if") path
else else
compiler_versions = compiler_versions =
case CompilerVersion.fetch_versions(:solc) |> debug("fetch version") do case CompilerVersion.fetch_versions(:solc) do
{:ok, compiler_versions} -> {:ok, compiler_versions} ->
compiler_versions compiler_versions
@ -102,7 +94,6 @@ defmodule Explorer.SmartContract.SolcDownloader do
download_path download_path
|> HTTPoison.get!([], timeout: 60_000, recv_timeout: 60_000) |> HTTPoison.get!([], timeout: 60_000, recv_timeout: 60_000)
|> debug("HTTPoison download solcjs")
|> Map.get(:body) |> Map.get(:body)
end end
end end

@ -10,8 +10,8 @@ defmodule Explorer.SmartContract.Solidity.PublishHelper do
alias Explorer.SmartContract.Solidity.Publisher alias Explorer.SmartContract.Solidity.Publisher
alias Explorer.ThirdPartyIntegrations.Sourcify alias Explorer.ThirdPartyIntegrations.Sourcify
def verify_and_publish(address_hash_string, files_array, conn, api_v2? \\ false) do def verify_and_publish(address_hash_string, files_array, conn, api_v2?, chosen_contract \\ nil) do
with {:ok, _verified_status} <- Sourcify.verify(address_hash_string, files_array), with {:ok, _verified_status} <- Sourcify.verify(address_hash_string, files_array, chosen_contract),
{:ok, _verified_status} <- Sourcify.check_by_address(address_hash_string) do {:ok, _verified_status} <- Sourcify.check_by_address(address_hash_string) do
get_metadata_and_publish(address_hash_string, conn, api_v2?) get_metadata_and_publish(address_hash_string, conn, api_v2?)
else else

@ -75,13 +75,13 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result}}], :on_demand) EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result}}], :on_demand)
end end
def perform({"sourcify_api_v2", address_hash_string, files_array, conn}) do def perform({"sourcify_api_v2", address_hash_string, files_array, conn, chosen_contract}) do
case Sourcify.check_by_address(address_hash_string) do case Sourcify.check_by_address(address_hash_string) do
{:ok, _verified_status} -> {:ok, _verified_status} ->
PublishHelper.get_metadata_and_publish(address_hash_string, conn, true) PublishHelper.get_metadata_and_publish(address_hash_string, conn, true)
_ -> _ ->
PublishHelper.verify_and_publish(address_hash_string, files_array, conn, true) PublishHelper.verify_and_publish(address_hash_string, files_array, conn, true, chosen_contract)
end end
end end

@ -46,6 +46,51 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
end end
end end
def publish(address_hash, params, files) do
case Verifier.evaluate_authenticity(address_hash, params, files) do
{
:ok,
%{
"abi" => abi_string,
"compiler_version" => _,
"constructor_arguments" => _,
"contract_libraries" => contract_libraries,
"contract_name" => contract_name,
"evm_version" => _,
"file_name" => file_name,
"optimization" => _,
"optimization_runs" => _,
"sources" => sources
} = result_params
} ->
secondary_sources =
for {file, source} <- sources,
file != file_name,
do: %{"file_name" => file, "contract_source_code" => source, "address_hash" => address_hash}
%{^file_name => contract_source_code} = sources
prepared_params =
result_params
|> Map.put("contract_source_code", contract_source_code)
|> Map.put("external_libraries", contract_libraries)
|> Map.put("name", contract_name)
|> Map.put("file_path", file_name)
|> Map.put("secondary_sources", secondary_sources)
publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string))
{:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi)
{:error, error} ->
{:error, unverified_smart_contract(address_hash, params, error, nil)}
_ ->
{:error, unverified_smart_contract(address_hash, params, "Unexpected error", nil)}
end
end
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)

@ -8,7 +8,7 @@ defmodule Explorer.SmartContract.Vyper.PublisherWorker do
alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.SmartContract.Vyper.Publisher alias Explorer.SmartContract.Vyper.Publisher
def perform({address_hash, params, conn}) do def perform({address_hash, params, %Plug.Conn{} = conn}) do
result = result =
case Publisher.publish(address_hash, params) do case Publisher.publish(address_hash, params) do
{:ok, _contract} = result -> {:ok, _contract} = result ->
@ -21,6 +21,19 @@ defmodule Explorer.SmartContract.Vyper.PublisherWorker do
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
end end
def perform({address_hash, params, files}) do
result =
case Publisher.publish(address_hash, params, files) do
{:ok, _contract} = result ->
result
{:error, changeset} ->
{:error, changeset}
end
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result}}], :on_demand)
end
def perform({address_hash, params}) do def perform({address_hash, params}) do
result = result =
case Publisher.publish(address_hash, params) do case Publisher.publish(address_hash, params) do

@ -32,6 +32,38 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
end end
end end
def evaluate_authenticity(address_hash, params, files) do
try do
if RustVerifierInterface.enabled?() do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input =
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
init
_ ->
""
end
params
|> Map.put("creation_bytecode", creation_tx_input)
|> Map.put("deployed_bytecode", deployed_bytecode)
|> Map.put("evm_version", params["evm_version"])
|> Map.put("sources", files)
|> RustVerifierInterface.vyper_verify_multipart()
end
rescue
exception ->
Logger.error(fn ->
[
"Error while verifying multi-part vyper smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}: ",
Exception.format(:error, exception)
]
end)
end
end
defp evaluate_authenticity_inner(true, address_hash, params) do defp evaluate_authenticity_inner(true, address_hash, params) do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
@ -47,6 +79,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
params params
|> Map.put("creation_bytecode", creation_tx_input) |> Map.put("creation_bytecode", creation_tx_input)
|> Map.put("deployed_bytecode", deployed_bytecode) |> Map.put("deployed_bytecode", deployed_bytecode)
|> Map.put("evm_version", params["evm_version"] || "istanbul")
|> Map.put("sources", %{"#{params["name"]}.vy" => params["contract_source_code"]}) |> Map.put("sources", %{"#{params["name"]}.vy" => params["contract_source_code"]})
|> RustVerifierInterface.vyper_verify_multipart() |> RustVerifierInterface.vyper_verify_multipart()
end end

@ -28,9 +28,9 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
http_get_request(get_metadata_full_url, []) http_get_request(get_metadata_full_url, [])
end end
def verify(address_hash_string, files) do def verify(address_hash_string, files, chosen_contract) do
if RustVerifierInterface.enabled?() do if RustVerifierInterface.enabled?() do
verify_via_rust_microservice(address_hash_string, files) verify_via_rust_microservice(address_hash_string, files, chosen_contract)
else else
verify_via_sourcify_server(address_hash_string, files) verify_via_sourcify_server(address_hash_string, files)
end end
@ -76,13 +76,14 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end) end)
end end
def verify_via_rust_microservice(address_hash_string, files) do def verify_via_rust_microservice(address_hash_string, files, chosen_contract) do
chain_id = config(__MODULE__, :chain_id) chain_id = config(__MODULE__, :chain_id)
body_params = body_params =
Map.new() Map.new()
|> Map.put("chain", chain_id) |> Map.put("chain", chain_id)
|> Map.put("address", address_hash_string) |> Map.put("address", address_hash_string)
|> add_chosen_contract(chosen_contract)
files_body = prepare_body_for_microservice(files) files_body = prepare_body_for_microservice(files)
@ -93,6 +94,24 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
http_post_request_rust_microservice(verify_url_rust_microservice(), body) http_post_request_rust_microservice(verify_url_rust_microservice(), body)
end end
defp add_chosen_contract(params, nil), do: params
defp add_chosen_contract(params, index) when is_binary(index) do
case Integer.parse(index) do
{integer, ""} ->
Map.put(params, "chosenContract", integer)
_ ->
params
end
end
defp add_chosen_contract(params, index) when is_number(index) do
Map.put(params, "chosenContract", index)
end
defp add_chosen_contract(params, _index), do: params
defp prepare_body_for_microservice(files) when is_map(files) do defp prepare_body_for_microservice(files) when is_map(files) do
files files
|> Enum.reduce(Map.new(), fn {name, content}, acc -> |> Enum.reduce(Map.new(), fn {name, content}, acc ->

Loading…
Cancel
Save