From e81f2ee75a917a1dedc8881c8f51da0d743950b2 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 5 Jun 2023 23:30:51 +0300 Subject: [PATCH] API and smart-contracts fixes (#7614) * Add icon url to search results * Move vyper evm versions to envs * Fix smart contracts reading * Fix review comments --- CHANGELOG.md | 3 +- ...ddress_contract_verification_controller.ex | 2 +- ...ification_via_flattened_code_controller.ex | 2 +- ...ication_via_multi_part_files_controller.ex | 2 +- .../api/v2/smart_contract_controller.ex | 1 + .../api/v2/verification_controller.ex | 5 +- .../controllers/smart_contract_controller.ex | 3 +- .../lib/block_scout_web/notifier.ex | 2 +- .../smart_contract/_functions.html.eex | 2 +- .../views/api/v2/search_view.ex | 3 +- .../views/api/v2/smart_contract_view.ex | 38 ++- .../views/smart_contract_view.ex | 46 +-- .../api/v2/smart_contract_controller_test.exs | 284 +++++++++++++++++- apps/explorer/lib/explorer/chain.ex | 18 +- .../lib/explorer/smart_contract/helper.ex | 3 +- .../lib/explorer/smart_contract/reader.ex | 49 ++- .../smart_contract/solidity/code_compiler.ex | 18 +- .../smart_contract/solidity/verifier.ex | 10 +- .../explorer/smart_contract/reader_test.exs | 8 +- .../solidity/code_compiler_test.exs | 14 +- config/runtime.exs | 9 +- docker-compose/envs/common-blockscout.env | 3 +- docker/Makefile | 7 +- 23 files changed, 446 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ca633f87..4db918c760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ - [#7635](https://github.com/blockscout/blockscout/pull/7635) - Fix single 1155 transfer displaying - [#7629](https://github.com/blockscout/blockscout/pull/7629) - Fix NFT fetcher +- [#7614](https://github.com/blockscout/blockscout/pull/7614) - API and smart-contracts fixes and improvements - [#7611](https://github.com/blockscout/blockscout/pull/7611) - Fix tokens pagination -- [#7566](https://github.com/blockscout/blockscout/pull/7566) - Account: check composed email beofre sending +- [#7566](https://github.com/blockscout/blockscout/pull/7566) - Account: check composed email before sending - [#7564](https://github.com/blockscout/blockscout/pull/7564) - Return contract type in address view - [#7562](https://github.com/blockscout/blockscout/pull/7562) - Remove fallback from Read methods - [#7537](https://github.com/blockscout/blockscout/pull/7537), [#7553](https://github.com/blockscout/blockscout/pull/7553) - Withdrawals fixes and improvements diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex index 9f2d689093..5a00556fac 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -38,7 +38,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions, - evm_versions: CodeCompiler.allowed_evm_versions(), + evm_versions: CodeCompiler.evm_versions(:solidity), address_hash: address_hash_string ) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex index 99017a86ee..fab2192cfa 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex @@ -33,7 +33,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions, - evm_versions: CodeCompiler.allowed_evm_versions(), + evm_versions: CodeCompiler.evm_versions(:solidity), address_hash: address_hash_string ) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex index cdc6d44b32..7d0f819e96 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex @@ -33,7 +33,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController d render(conn, "new.html", changeset: changeset, address_hash: address_hash_string, - evm_versions: CodeCompiler.allowed_evm_versions(), + evm_versions: CodeCompiler.evm_versions(:solidity), compiler_versions: compiler_versions ) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index dc294bed73..8d016c4047 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -176,6 +176,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do contract_type, params["from"], address.smart_contract.abi, + true, @api_true ) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex index 60ee9fbc13..2463d5792d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex @@ -16,7 +16,6 @@ defmodule BlockScoutWeb.API.V2.VerificationController do @api_true [api?: true] def config(conn, _params) do - evm_versions = CodeCompiler.allowed_evm_versions() solidity_compiler_versions = CompilerVersion.fetch_version_list(:solc) vyper_compiler_versions = CompilerVersion.fetch_version_list(:vyper) @@ -31,11 +30,11 @@ defmodule BlockScoutWeb.API.V2.VerificationController do conn |> json(%{ - solidity_evm_versions: evm_versions, + solidity_evm_versions: CodeCompiler.evm_versions(:solidity), solidity_compiler_versions: solidity_compiler_versions, vyper_compiler_versions: vyper_compiler_versions, verification_options: verification_options, - vyper_evm_versions: ["byzantium", "constantinople", "petersburg", "istanbul"], + vyper_evm_versions: CodeCompiler.evm_versions(:vyper), is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?() }) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 0ba2f294ad..b403e0e7d5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -196,7 +196,8 @@ defmodule BlockScoutWeb.SmartContractController do %{method_id: params["method_id"], args: args}, contract_type, params["from"], - address.smart_contract && address.smart_contract.abi + address.smart_contract && address.smart_contract.abi, + true ) end diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index e70ff42029..7ca4a892a8 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -80,7 +80,7 @@ defmodule BlockScoutWeb.Notifier do |> View.render_to_string("new.html", changeset: changeset, compiler_versions: compiler_versions, - evm_versions: CodeCompiler.allowed_evm_versions(), + evm_versions: CodeCompiler.evm_versions(:solidity), address_hash: address_hash, conn: conn, retrying: true diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex index ced64b2c06..af6885a211 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -145,7 +145,7 @@ <% else %> -
"><%= raw(values_with_type(output["value"], output["type"], [output["name"]], 0, output["components"])) %>
+
"><%= raw(values_with_type(output["value"], output["type"], fetch_name(function["names"], index), 0)) %>
<% end %> <% end %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index bddd3b60d9..a8ac5cc644 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -23,7 +23,8 @@ defmodule BlockScoutWeb.API.V2.SearchView do "symbol" => search_result.symbol, "address" => search_result.address_hash, "token_url" => token_path(Endpoint, :show, search_result.address_hash), - "address_url" => address_path(Endpoint, :show, search_result.address_hash) + "address_url" => address_path(Endpoint, :show, search_result.address_hash), + "icon_url" => search_result.icon_url } end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index bfe8f1d09d..4fc3b77994 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -98,7 +98,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do %{result: %{error: error}, is_error: true} _ -> - %{result: %{output: outputs, names: names}, is_error: false} + %{result: %{output: Enum.map(outputs, &render_json/1), names: names}, is_error: false} end end @@ -118,13 +118,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do function |> Map.drop(["abi_outputs"]) - outputs = Enum.map(result["outputs"], &prepare_output/1) + outputs = result["outputs"] |> Enum.map(&prepare_output/1) Map.replace(result, "outputs", outputs) end end defp prepare_output(%{"type" => type, "value" => value} = output) do - Map.replace(output, "value", ABIEncodedValueView.value_json(type, value)) + Map.replace(output, "value", render_json(value, type)) end defp prepare_output(output), do: output @@ -277,4 +277,36 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do "solidity" end end + + def render_json(%{"type" => type, "value" => value}) do + %{"type" => type, "value" => render_json(value, type)} + end + + def render_json(value, type) when type in [:address, "address", "address payable"] do + SmartContractView.cast_address(value) + end + + def render_json(value, type) when type in [:string, "string"] do + to_string(value) + end + + def render_json(value, type) when is_tuple(value) do + value + |> SmartContractView.zip_tuple_values_with_types(type) + |> Enum.map(fn {type, value} -> + render_json(value, type) + end) + end + + def render_json(value, type) when is_list(value) do + value |> Enum.map(&render_json(&1, type)) + end + + def render_json(value, _type) when is_binary(value) do + SmartContractView.binary_to_utf_string(value) + end + + def render_json(value, _type) do + value + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex index 39297f2956..4a96a93adb 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex @@ -6,6 +6,8 @@ defmodule BlockScoutWeb.SmartContractView do alias Explorer.Chain.Hash.Address, as: HashAddress alias Explorer.SmartContract.Helper + require Logger + def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) def queryable?(inputs) when is_nil(inputs), do: false @@ -63,7 +65,7 @@ defmodule BlockScoutWeb.SmartContractView do String.starts_with?(type, "address") -> values = value - |> Enum.map_join(", ", &binary_to_utf_string(&1)) + |> Enum.map_join(", ", &cast_address(&1)) render_array_type_value(type, values, fetch_name(names, index)) @@ -117,6 +119,17 @@ defmodule BlockScoutWeb.SmartContractView do def values_with_type(value, :error, _components), do: render_type_value("error", Helper.sanitize_input(value), "error") + def cast_address(value) do + case HashAddress.cast(value) do + {:ok, address} -> + to_string(address) + + _ -> + Logger.warn(fn -> ["Error decoding address value: #{inspect(value)}"] end) + "(decoding error)" + end + end + defp fetch_name(nil, _index), do: nil defp fetch_name([], _index), do: nil @@ -137,6 +150,15 @@ defmodule BlockScoutWeb.SmartContractView do end defp tuple_to_array(value, type, names) do + value + |> zip_tuple_values_with_types(type) + |> Enum.with_index() + |> Enum.map(fn {{type, value}, index} -> + values_with_type(value, type, fetch_name(names, index), 0) + end) + end + + def zip_tuple_values_with_types(value, type) do types_string = type |> String.slice(6..-2) @@ -165,16 +187,10 @@ defmodule BlockScoutWeb.SmartContractView do value |> Tuple.to_list() - values_types_list = Enum.zip(tuple_types, values_list) - - values_types_list - |> Enum.with_index() - |> Enum.map(fn {{type, value}, index} -> - values_with_type(value, type, fetch_name(names, index), 0) - end) + Enum.zip(tuple_types, values_list) end - defp compose_array_if_to_merge(arr, val, to_merge) do + def compose_array_if_to_merge(arr, val, to_merge) do if count_string_symbols(val)["]"] > count_string_symbols(val)["["] do updated_arr = update_last_list_item(arr, val) {updated_arr, !to_merge} @@ -184,7 +200,7 @@ defmodule BlockScoutWeb.SmartContractView do end end - defp compose_array_else(arr, val, to_merge) do + def compose_array_else(arr, val, to_merge) do if count_string_symbols(val)["["] > count_string_symbols(val)["]"] do # credo:disable-for-next-line {arr ++ [val], !to_merge} @@ -214,7 +230,7 @@ defmodule BlockScoutWeb.SmartContractView do end) end - defp binary_to_utf_string(item) do + def binary_to_utf_string(item) do case Integer.parse(to_string(item)) do {item_integer, ""} -> to_string(item_integer) @@ -229,11 +245,7 @@ defmodule BlockScoutWeb.SmartContractView do end defp add_0x(item) do - if String.starts_with?(item, "0x") do - item - else - "0x" <> Base.encode16(item, case: :lower) - end + "0x" <> Base.encode16(item, case: :lower) end defp render_type_value(type, value, type) do @@ -250,7 +262,7 @@ defmodule BlockScoutWeb.SmartContractView do render_type_value(type, value_to_display, name) end - defp supplement_type_with_components(type, components) do + def supplement_type_with_components(type, components) do if type == "tuple" && components do types = components diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 7525beafd3..d404d3655c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -462,24 +462,83 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "name" => "disableWhitelist", "inputs" => [%{"type" => "bool", "name" => "disable", "internalType" => "bool"}] }, - %{"type" => "fallback"} + %{"type" => "fallback"}, + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [ + %{ + "type" => "tuple", + "name" => "", + "internalType" => "struct Storage.TransactionReceipt", + "components" => [ + %{"type" => "bytes32", "name" => "txHash", "internalType" => "bytes32"}, + %{"type" => "uint256", "name" => "blockNumber", "internalType" => "uint256"}, + %{"type" => "bytes32", "name" => "blockHash", "internalType" => "bytes32"}, + %{"type" => "uint256", "name" => "transactionIndex", "internalType" => "uint256"}, + %{"type" => "address", "name" => "from", "internalType" => "address"}, + %{"type" => "address", "name" => "to", "internalType" => "address"}, + %{"type" => "uint256", "name" => "gasUsed", "internalType" => "uint256"}, + %{"type" => "bool", "name" => "status", "internalType" => "bool"}, + %{ + "type" => "tuple[]", + "name" => "logs", + "internalType" => "struct Storage.Log[]", + "components" => [ + %{"type" => "address", "name" => "from", "internalType" => "address"}, + %{"type" => "bytes32[]", "name" => "topics", "internalType" => "bytes32[]"}, + %{"type" => "bytes", "name" => "data", "internalType" => "bytes"} + ] + } + ] + } + ], + "name" => "retrieve", + "inputs" => [] + } ] target_contract = insert(:smart_contract, abi: abi) blockchain_eth_call_mock() - request = get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read") + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: id, + method: "eth_call", + params: [%{to: _address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"}, _] + } + ], + _opts -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: + "0x0000000000000000000000000000000000000000000000000000000000000020fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab200000000000000000000000000000000000000000000000000000000000003e8fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000001e0f30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000" + } + ]} + end + ) + + request = + get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read", %{ + "from" => "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E" + }) + assert response = json_response(request, 200) assert %{ "type" => "function", "stateMutability" => "view", + "names" => ["address"], "outputs" => [ %{ "type" => "address", - "name" => "", - "internalType" => "address", "value" => "0xfffffffffffffffffffffffffffffffffffffffe" } ], @@ -497,6 +556,75 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "method_id" => "c683630d" } in response + assert %{ + "inputs" => [], + "method_id" => "2e64cec1", + "name" => "retrieve", + "names" => [ + [ + "struct Storage.TransactionReceipt", + [ + "txHash", + "blockNumber", + "blockHash", + "transactionIndex", + "from", + "to", + "gasUsed", + "status", + ["logs", ["from", "topics", "data"]] + ] + ] + ], + "outputs" => [ + %{ + "type" => + "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]", + "value" => [ + "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", + 1000, + "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", + 10, + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + 123_123, + true, + [ + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ], + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ], + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ] + ] + ] + } + ], + "stateMutability" => "view", + "type" => "function" + } in response + refute %{"type" => "fallback"} in response end @@ -544,10 +672,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "type" => "function", "stateMutability" => "view", "payable" => false, + "names" => [nil], "outputs" => [ %{ "type" => "address[]", - "name" => "", "value" => [ "0x64631b5d259ead889e8b06d12c8b74742804e5f1", "0x234fe7224ce480ca97d01897311b8c3d35162f86", @@ -666,6 +794,146 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do } == response end + test "query complex response", %{conn: conn} do + abi = [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [ + %{ + "type" => "tuple", + "name" => "", + "internalType" => "struct Storage.TransactionReceipt", + "components" => [ + %{"type" => "bytes32", "name" => "txHash", "internalType" => "bytes32"}, + %{"type" => "uint256", "name" => "blockNumber", "internalType" => "uint256"}, + %{"type" => "bytes32", "name" => "blockHash", "internalType" => "bytes32"}, + %{"type" => "uint256", "name" => "transactionIndex", "internalType" => "uint256"}, + %{"type" => "address", "name" => "from", "internalType" => "address"}, + %{"type" => "address", "name" => "to", "internalType" => "address"}, + %{"type" => "uint256", "name" => "gasUsed", "internalType" => "uint256"}, + %{"type" => "bool", "name" => "status", "internalType" => "bool"}, + %{ + "type" => "tuple[]", + "name" => "logs", + "internalType" => "struct Storage.Log[]", + "components" => [ + %{"type" => "address", "name" => "from", "internalType" => "address"}, + %{"type" => "bytes32[]", "name" => "topics", "internalType" => "bytes32[]"}, + %{"type" => "bytes", "name" => "data", "internalType" => "bytes"} + ] + } + ] + } + ], + "name" => "retrieve", + "inputs" => [] + } + ] + + target_contract = insert(:smart_contract, abi: abi) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: id, + method: "eth_call", + params: [%{to: _address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"}, _] + } + ], + _opts -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: + "0x0000000000000000000000000000000000000000000000000000000000000020fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab200000000000000000000000000000000000000000000000000000000000003e8fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000001e0f30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000" + } + ]} + end + ) + + request = + post(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/query-read-method", %{ + "contract_type" => "regular", + "args" => [], + "method_id" => "2e64cec1", + "from" => "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E" + }) + + assert response = json_response(request, 200) + + assert %{ + "is_error" => false, + "result" => %{ + "names" => [ + [ + "struct Storage.TransactionReceipt", + [ + "txHash", + "blockNumber", + "blockHash", + "transactionIndex", + "from", + "to", + "gasUsed", + "status", + ["logs", ["from", "topics", "data"]] + ] + ] + ], + "output" => [ + %{ + "type" => + "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]", + "value" => [ + "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", + 1000, + "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", + 10, + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + 123_123, + true, + [ + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ], + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ], + [ + "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", + [ + "0x3078300000000000000000000000000000000000000000000000000000000000", + "0x3078303031313232333300000000000000000000000000000000000000000000", + "0x3078303031313232333331323300000000000000000000000000000000000000" + ], + "0x307830303030313233313233" + ] + ] + ] + } + ] + } + } == response + end + test "query-read-method with nonexistent method_id", %{conn: conn} do abi = [ %{ @@ -1041,11 +1309,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert %{ "type" => "function", "stateMutability" => "view", + "names" => ["address"], "outputs" => [ %{ "type" => "address", - "name" => "", - "internalType" => "address", "value" => "0xfffffffffffffffffffffffffffffffffffffffe" } ], @@ -1220,11 +1487,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert %{ "type" => "function", "stateMutability" => "view", + "names" => ["address"], "outputs" => [ %{ "type" => "address", - "name" => "", - "internalType" => "address", "value" => "0xfffffffffffffffffffffffffffffffffffffffe" } ], diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index cbf78ee991..b43d383ce4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1512,7 +1512,8 @@ defmodule Explorer.Chain do symbol: token.symbol, holder_count: token.holder_count, inserted_at: token.inserted_at, - block_number: 0 + block_number: 0, + icon_url: token.icon_url } ) end @@ -1531,7 +1532,8 @@ defmodule Explorer.Chain do symbol: ^nil, holder_count: ^nil, inserted_at: address.inserted_at, - block_number: 0 + block_number: 0, + icon_url: nil } ) end @@ -1559,7 +1561,8 @@ defmodule Explorer.Chain do symbol: ^nil, holder_count: ^nil, inserted_at: address.inserted_at, - block_number: 0 + block_number: 0, + icon_url: nil } ) @@ -1582,7 +1585,8 @@ defmodule Explorer.Chain do symbol: ^nil, holder_count: ^nil, inserted_at: transaction.inserted_at, - block_number: 0 + block_number: 0, + icon_url: nil } ) @@ -1605,7 +1609,8 @@ defmodule Explorer.Chain do symbol: ^nil, holder_count: ^nil, inserted_at: block.inserted_at, - block_number: block.number + block_number: block.number, + icon_url: nil } ) @@ -1623,7 +1628,8 @@ defmodule Explorer.Chain do symbol: ^nil, holder_count: ^nil, inserted_at: block.inserted_at, - block_number: block.number + block_number: block.number, + icon_url: nil } ) diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index c2476ee713..cffc145b35 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -24,7 +24,8 @@ defmodule Explorer.SmartContract.Helper do @spec read_with_wallet_method?(%{}) :: true | false def read_with_wallet_method?(function), do: - !error?(function) && !event?(function) && !constructor?(function) && !fallback?(function) && nonpayable?(function) && + !error?(function) && !event?(function) && !constructor?(function) && !fallback?(function) && + nonpayable?(function) && !empty_outputs?(function) def empty_inputs?(function), do: function["inputs"] == [] diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 6f0d3fa938..4fa3e8dadf 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -270,7 +270,7 @@ defmodule Explorer.SmartContract.Reader do abi_with_method_id |> Enum.filter(&Helper.queriable_method?(&1)) - |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, from)) + |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, [], from)) end def read_only_functions_from_abi_with_sender(_, _, _), do: [] @@ -332,21 +332,34 @@ defmodule Explorer.SmartContract.Reader do "tuple[#{tuple_types}]" end - def fetch_current_value_from_blockchain(function, abi, contract_address_hash, leave_error_as_map, from \\ nil) do + def fetch_current_value_from_blockchain( + function, + abi, + contract_address_hash, + leave_error_as_map, + options, + from \\ nil + ) do case function do %{"inputs" => []} -> method_id = function["method_id"] args = function["inputs"] - outputs = function["outputs"] - values = - contract_address_hash - |> query_verified_contract(%{method_id => normalize_args(args)}, from, leave_error_as_map, abi) - |> link_outputs_and_values(outputs, method_id) + %{output: outputs, names: names} = + query_function_with_names( + contract_address_hash, + %{method_id: method_id, args: args}, + :regular, + from, + abi, + leave_error_as_map, + options + ) function - |> Map.replace!("outputs", values) + |> Map.replace!("outputs", outputs) |> Map.put("abi_outputs", Map.get(function, "outputs", [])) + |> Map.put("names", names) _ -> function @@ -364,9 +377,10 @@ defmodule Explorer.SmartContract.Reader do %{method_id: String.t(), args: [term()] | nil}, :regular | :proxy, String.t() | nil, - [api?] + [], + boolean() ) :: %{:names => [any()], :output => [%{}]} - def query_function_with_names(contract_address_hash, params, type, from, abi, options \\ []) + def query_function_with_names(contract_address_hash, params, type, from, abi, leave_error_as_map, options \\ []) def query_function_with_names( contract_address_hash, @@ -374,6 +388,7 @@ defmodule Explorer.SmartContract.Reader do :regular, from, abi, + leave_error_as_map, _options ) do outputs = @@ -382,7 +397,7 @@ defmodule Explorer.SmartContract.Reader do method_id, args || [], from, - true, + leave_error_as_map, abi ) @@ -390,7 +405,15 @@ defmodule Explorer.SmartContract.Reader do %{output: outputs, names: names} end - def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, :proxy, from, _abi, options) do + def query_function_with_names( + contract_address_hash, + %{method_id: method_id, args: args}, + :proxy, + from, + _abi, + leave_error_as_map, + options + ) do abi = get_abi(contract_address_hash, :proxy, options) outputs = @@ -399,7 +422,7 @@ defmodule Explorer.SmartContract.Reader do method_id, args || [], from, - true, + leave_error_as_map, abi ) diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index b08e64c970..b63d01c57e 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -76,14 +76,14 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do code = Keyword.fetch!(params, :code) optimize = Keyword.fetch!(params, :optimize) optimization_runs = optimization_runs(params) - evm_version = Keyword.get(params, :evm_version, List.last(allowed_evm_versions())) + evm_version = Keyword.get(params, :evm_version, List.last(evm_versions(:solidity))) bytecode_hash = Keyword.get(params, :bytecode_hash, "default") external_libs = Keyword.get(params, :external_libs, %{}) external_libs_string = Jason.encode!(external_libs) checked_evm_version = - if evm_version in allowed_evm_versions() do + if evm_version in evm_versions(:solidity) do evm_version else "byzantium" @@ -262,9 +262,19 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do end end - def allowed_evm_versions do + def evm_versions(compiler_type) do + case compiler_type do + :vyper -> + allowed_evm_versions(:allowed_vyper_evm_versions) + + :solidity -> + allowed_evm_versions(:allowed_solidity_evm_versions) + end + end + + defp allowed_evm_versions(env_name) do :explorer - |> Application.get_env(:allowed_evm_versions) + |> Application.get_env(env_name) |> String.split(",") |> Enum.map(fn version -> String.trim(version) end) end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index a41847ad3c..57c11709ab 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -59,7 +59,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do if is_nil(params["name"]) or params["name"] == "" do {:error, :name} else - latest_evm_version = List.last(CodeCompiler.allowed_evm_versions()) + latest_evm_version = List.last(CodeCompiler.evm_versions(:solidity)) evm_version = Map.get(params, "evm_version", latest_evm_version) all_versions = [evm_version | previous_evm_versions(evm_version)] @@ -505,19 +505,19 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end def previous_evm_versions(current_evm_version) do - index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end) + index = Enum.find_index(CodeCompiler.evm_versions(:solidity), fn el -> el == current_evm_version end) cond do index == 0 -> [] index == 1 -> - [List.first(CodeCompiler.allowed_evm_versions())] + [List.first(CodeCompiler.evm_versions(:solidity))] true -> [ - Enum.at(CodeCompiler.allowed_evm_versions(), index - 1), - Enum.at(CodeCompiler.allowed_evm_versions(), index - 2) + Enum.at(CodeCompiler.evm_versions(:solidity), index - 1), + Enum.at(CodeCompiler.evm_versions(:solidity), index - 2) ] end end diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs index df6ed4727b..8eb6e1a2ec 100644 --- a/apps/explorer/test/explorer/smart_contract/reader_test.exs +++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs @@ -153,7 +153,7 @@ defmodule Explorer.SmartContract.ReaderTest do "constant" => true, "inputs" => [], "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}], + "outputs" => [%{"type" => "uint256", "value" => 0}], "payable" => _, "stateMutability" => _, "type" => _ @@ -162,7 +162,7 @@ defmodule Explorer.SmartContract.ReaderTest do "constant" => true, "inputs" => [%{"name" => "x", "type" => "uint256"}], "name" => "with_arguments", - "outputs" => [%{"name" => "", "type" => "bool"}], + "outputs" => [%{"type" => "bool"}], "payable" => _, "stateMutability" => _, "type" => _ @@ -238,7 +238,7 @@ defmodule Explorer.SmartContract.ReaderTest do "constant" => true, "inputs" => [], "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}], + "outputs" => [%{"type" => "uint256", "value" => 0}], "payable" => _, "stateMutability" => _, "type" => _ @@ -247,7 +247,7 @@ defmodule Explorer.SmartContract.ReaderTest do "constant" => true, "inputs" => [%{"name" => "x", "type" => "uint256"}], "name" => "with_arguments", - "outputs" => [%{"name" => "", "type" => "bool"}], + "outputs" => [%{"type" => "bool"}], "payable" => _, "stateMutability" => _, "type" => _ diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs index 952c320ebf..ed80cb1219 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs @@ -353,17 +353,17 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do end end - # describe "allowed_evm_versions/0" do + # describe "allowed_solidity_evm_versions/0" do # test "returns allowed evm versions defined by ALLOWED_EVM_VERSIONS env var" do - # Application.put_env(:explorer, :allowed_evm_versions, "CustomEVM1,CustomEVM2,CustomEVM3") - # response = CodeCompiler.allowed_evm_versions() + # Application.put_env(:explorer, :allowed_solidity_evm_versions, "CustomEVM1,CustomEVM2,CustomEVM3") + # response = CodeCompiler.evm_versions(:solidity) # assert ["CustomEVM1", "CustomEVM2", "CustomEVM3"] = response # end # test "returns allowed evm versions defined by not trimmed ALLOWED_EVM_VERSIONS env var" do - # Application.put_env(:explorer, :allowed_evm_versions, "CustomEVM1, CustomEVM2, CustomEVM3") - # response = CodeCompiler.allowed_evm_versions() + # Application.put_env(:explorer, :allowed_solidity_evm_versions, "CustomEVM1, CustomEVM2, CustomEVM3") + # response = CodeCompiler.evm_versions(:solidity) # assert ["CustomEVM1", "CustomEVM2", "CustomEVM3"] = response # end @@ -371,11 +371,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do # test "returns default_allowed_evm_versions" do # Application.put_env( # :explorer, - # :allowed_evm_versions, + # :allowed_solidity_evm_versions, # "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg" # ) - # response = CodeCompiler.allowed_evm_versions() + # response = CodeCompiler.evm_versions(:solidity) # assert ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg"] = response # end diff --git a/config/runtime.exs b/config/runtime.exs index dd7e25d532..5f5de06c14 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -170,9 +170,12 @@ exchange_rates_coin = System.get_env("EXCHANGE_RATES_COIN") config :explorer, coin: System.get_env("COIN") || exchange_rates_coin || "ETH", coin_name: System.get_env("COIN_NAME") || exchange_rates_coin || "ETH", - allowed_evm_versions: - System.get_env("CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS") || - "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,default", + allowed_solidity_evm_versions: + System.get_env("CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS") || + "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default", + allowed_vyper_evm_versions: + System.get_env("CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS") || + "byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default", include_uncles_in_average_block_time: ConfigHelper.parse_bool_env_var("UNCLES_IN_AVERAGE_BLOCK_TIME"), healthy_blocks_period: ConfigHelper.parse_time_env_var("HEALTHY_BLOCKS_PERIOD", "5m"), realtime_events_sender: diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 5f67ecace9..b5717e3b73 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -81,7 +81,8 @@ CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800 CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=3600 CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800 TOKEN_METADATA_UPDATE_INTERVAL=172800 -CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,default +CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default +CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default # CONTRACT_VERIFICATION_MAX_LIBRARIES=10 CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 # CONTRACT_DISABLE_INTERACTION= diff --git a/docker/Makefile b/docker/Makefile index fff407f212..ce4806af8c 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -690,8 +690,11 @@ endif ifdef DECODE_NOT_A_CONTRACT_CALLS BLOCKSCOUT_CONTAINER_PARAMS += -e 'DECODE_NOT_A_CONTRACT_CALLS=$(DECODE_NOT_A_CONTRACT_CALLS)' endif -ifdef CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS - BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS=$(CONTRACT_VERIFICATION_ALLOWED_EVM_VERSIONS)' +ifdef CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=$(CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS)' +endif +ifdef CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=$(CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS)' endif ifdef CONTRACT_VERIFICATION_MAX_LIBRARIES BLOCKSCOUT_CONTAINER_PARAMS += -e 'CONTRACT_VERIFICATION_MAX_LIBRARIES=$(CONTRACT_VERIFICATION_MAX_LIBRARIES)'