diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index ae03f7ab4573..4ea8e9834af8 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -40,7 +40,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -100,7 +100,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -124,7 +124,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -147,7 +147,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -187,7 +187,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -213,7 +213,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -242,7 +242,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -287,7 +287,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -333,7 +333,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -390,7 +390,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -444,7 +444,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -509,7 +509,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -573,7 +573,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_15-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_16-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index b53f8af63bae..8bc93ef78795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#7187](https://github.com/blockscout/blockscout/pull/7187) - Integrate [Eth Bytecode DB](https://github.com/blockscout/blockscout-rs/tree/main/eth-bytecode-db/eth-bytecode-db) - [#7185](https://github.com/blockscout/blockscout/pull/7185) - Aave v3 transaction actions indexer - [#7148](https://github.com/blockscout/blockscout/pull/7148), [#7244](https://github.com/blockscout/blockscout/pull/7244) - API v2 improvements: API rate limiting, `/tokens/{address_hash}/instances/{token_id}/holders` and other changes 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 fdcd643b0926..423d13ce194b 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -225,6 +225,10 @@ defmodule BlockScoutWeb.Notifier do Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{}) end + def handle_event({:chain_event, :smart_contract_was_verified, :on_demand, [address_hash]}) do + Endpoint.broadcast("addresses:#{to_string(address_hash)}", "smart_contract_was_verified", %{}) + end + def handle_event(_), do: nil def fetch_compiler_version(compiler) do diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index 987f9370eb04..7c0a05969912 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -30,6 +30,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:contract_verification_result, :on_demand) Subscriber.to(:token_total_supply, :on_demand) Subscriber.to(:changed_bytecode, :on_demand) + Subscriber.to(:smart_contract_was_verified, :on_demand) # Does not come from the indexer Subscriber.to(:exchange_rate) Subscriber.to(:transaction_stats) 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 58dcd2105d53..bebd0fe7aa5a 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 @@ -216,7 +216,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do } end - def format_constructor_arguments(abi, constructor_arguments) do + def format_constructor_arguments(abi, constructor_arguments) + when not is_nil(abi) and not is_nil(constructor_arguments) do constructor_abi = Enum.find(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1) @@ -242,6 +243,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do nil end + def format_constructor_arguments(_abi, _constructor_arguments), do: nil + defp prepare_smart_contract_for_list(%SmartContract{} = smart_contract) do token = smart_contract.address.token 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 971179f208fd..53ab2b7a97b5 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 @@ -1,11 +1,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do use BlockScoutWeb.ConnCase + use BlockScoutWeb.ChannelCase, async: false import Mox alias BlockScoutWeb.AddressContractView alias BlockScoutWeb.Models.UserFromAuth alias Explorer.Chain.{Address, SmartContract} + alias Plug.Conn setup :set_mox_from_context @@ -295,6 +297,117 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert correct_response == response end + + test "automatically verify contract via Eth Bytecode Interface", %{conn: conn} do + {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([]) + + bypass = Bypass.open() + eth_bytecode_response = File.read!("./test/support/fixture/smart_contract/eth_bytecode_db_search_response.json") + + old_env = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour) + + Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, + service_url: "http://localhost:#{bypass.port}", + enabled: true + ) + + address = insert(:contract_address) + + insert(:transaction, + created_contract_address_hash: address.hash, + input: + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029" + ) + |> with_block() + + topic = "addresses:#{address.hash}" + + {:ok, _reply, _socket} = + BlockScoutWeb.UserSocketV2 + |> socket("no_id", %{}) + |> subscribe_and_join(topic) + + Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn -> + Conn.resp(conn, 200, eth_bytecode_response) + end) + + request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_verified", + topic: ^topic + }, + :timer.seconds(1) + + response = json_response(request, 200) + + assert response == + %{ + "is_self_destructed" => false, + "deployed_bytecode" => to_string(address.contract_code), + "creation_bytecode" => + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029" + } + + request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert %{"is_verified" => true} = json_response(request, 200) + + Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env) + Bypass.down(bypass) + GenServer.stop(pid) + end + + test "check fetch interval for LookUpSmartContractSourcesOnDemand", %{conn: conn} do + {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([]) + + bypass = Bypass.open() + address = insert(:contract_address) + + insert(:transaction, + created_contract_address_hash: address.hash, + input: + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029" + ) + |> with_block() + + old_env = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour) + + Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, + service_url: "http://localhost:#{bypass.port}", + enabled: true + ) + + old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand) + + Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: 0) + + Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn -> + Conn.resp(conn, 200, "{\"sources\": []}") + end) + + _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + + Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn -> + Conn.resp(conn, 200, "{\"sources\": []}") + end) + + _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + + Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: 10000) + + Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources:search", fn conn -> + Conn.resp(conn, 200, "{\"sources\": []}") + end) + + _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + + Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, old_interval_env) + Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env) + Bypass.down(bypass) + GenServer.stop(pid) + end end describe "/smart-contracts/{address_hash}/methods-read" do diff --git a/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_response.json b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_response.json new file mode 100644 index 000000000000..a0ae7946d482 --- /dev/null +++ b/apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_response.json @@ -0,0 +1,17 @@ +{ + "sources": [ + { + "fileName": "Test.sol", + "contractName": "Test", + "compilerVersion": "v0.8.17+commit.8df45f5f", + "compilerSettings": "{\"libraries\":{\"Test.sol\":{}},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":199},\"outputSelection\":{\"*\":{\"\":[\"ast\"],\"*\":[\"abi\",\"evm.bytecode\",\"evm.deployedBytecode\",\"evm.methodIdentifiers\"]}}}", + "sourceType": "SOLIDITY", + "sourceFiles": { + "Test.sol": "// SPDX-License-Identifier: MIT\r\n\r\npragma solidity 0.8.17;\r\n\r\ncontract Test {\r\n enum E {\r\n V1, V2, V3, V4\r\n }\r\n struct A {\r\n E a;\r\n uint256[] b;\r\n B[] c;\r\n }\r\n\r\n struct B {\r\n uint256 d;\r\n uint256 e;\r\n }\r\n\r\n function get(uint256 x) external pure returns (A memory) {\r\n uint256[] memory b = new uint256[](3);\r\n b[0] = 1;\r\n b[1] = 2;\r\n b[2] = 3;\r\n B[] memory c = new B[](3);\r\n c[0] = B(1, 2);\r\n c[1] = B(3, 4);\r\n c[2] = B(5, 6);\r\n return A(E.V3, b, c);\r\n }\r\n}" + }, + "abi": "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"get\",\"outputs\":[{\"components\":[{\"type\":\"uint8\"},{\"type\":\"uint256[]\"},{\"components\":[{\"type\":\"uint256\"},{\"type\":\"uint256\"}],\"type\":\"tuple[]\"}],\"internalType\":\"struct Test.A\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + "constructorArguments": null, + "matchType": "PARTIAL" + } + ] +} \ No newline at end of file diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 04cd9939f719..b53167512bae 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -111,7 +111,8 @@ defmodule Explorer.Application do configure(MinMissingBlockNumber), configure(TokenTransferTokenIdMigration.Supervisor), configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand), - configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand), + sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand) ] |> List.flatten() end @@ -128,6 +129,16 @@ defmodule Explorer.Application do end end + defp sc_microservice_configure(process) do + config = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, []) + + if config[:enabled] && config[:type] == "eth_bytecode_db" do + process + else + [] + end + end + defp datadog_port do Application.get_env(:explorer, :datadog)[:port] end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index c20b1e5d1d78..f797996cb3d8 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -80,7 +80,7 @@ defmodule Explorer.Chain do alias Explorer.Chain.Cache.Block, as: BlockCache alias Explorer.Chain.Cache.Helper, as: CacheHelper - alias Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand + alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand} alias Explorer.Chain.Import.Runner alias Explorer.Chain.InternalTransaction.{CallType, Type} @@ -1950,8 +1950,11 @@ defmodule Explorer.Chain do %{smart_contract: smart_contract} -> if smart_contract do CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract) + LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract) address_result else + LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil) + address_verified_twin_contract = get_minimal_proxy_template(hash, options) || get_address_verified_twin_contract(hash, options).verified_contract @@ -1960,6 +1963,7 @@ defmodule Explorer.Chain do end _ -> + LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil) address_result end @@ -4428,6 +4432,13 @@ defmodule Explorer.Chain do end end + @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil + def address_hash_to_one_smart_contract(hash) do + SmartContract + |> where([sc], sc.address_hash == ^hash) + |> Repo.one() + end + @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: SmartContract.t() | nil def address_hash_to_smart_contract_without_twin(address_hash, options) do query = diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex index 08897fd5519f..92f01bfc844d 100644 --- a/apps/explorer/lib/explorer/chain/events/publisher.ex +++ b/apps/explorer/lib/explorer/chain/events/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do Publishes events related to the Chain context. """ - @allowed_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode)a + @allowed_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified)a def broadcast(_data, false), do: :ok diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index c15a08fbb4cb..9862e2e6c043 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do Subscribes to events related to the Chain context. """ - @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode)a + @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a diff --git a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex new file mode 100644 index 000000000000..cc8b90c3cf0d --- /dev/null +++ b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex @@ -0,0 +1,112 @@ +defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do + @moduledoc """ + On demand fetcher sources for unverified smart contract from [Ethereum Bytecode DB](https://github.com/blockscout/blockscout-rs/tree/main/eth-bytecode-db/eth-bytecode-db) + """ + + use GenServer + + alias Explorer.Chain + alias Explorer.Chain.{Address, Data, SmartContract} + alias Explorer.Chain.Events.Publisher + alias Explorer.SmartContract.EthBytecodeDBInterface + alias Explorer.SmartContract.Solidity.Publisher, as: SolidityPublisher + alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher + + import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1] + + @cache_name :smart_contracts_sources_fetching + + def trigger_fetch(nil, _) do + :ignore + end + + def trigger_fetch(_address, %SmartContract{}) do + :ignore + end + + def trigger_fetch(address, _) do + GenServer.cast(__MODULE__, {:fetch, address}) + end + + defp fetch_sources(address) do + creation_tx_input = contract_creation_input(address.hash) + + with {:ok, %{"sourceType" => type} = source} <- + %{} + |> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address.contract_code)) + |> EthBytecodeDBInterface.search_contract(), + {:ok, _} <- process_contract_source(type, source, address.hash) do + Publisher.broadcast(%{smart_contract_was_verified: [address.hash]}, :on_demand) + else + _ -> + false + end + + :ets.insert(@cache_name, {to_string(address.hash), DateTime.utc_now()}) + end + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(opts) do + :ets.new(@cache_name, [ + :set, + :named_table, + :public + ]) + + {:ok, opts} + end + + @impl true + def handle_cast({:fetch, address}, state) do + if need_to_fetch_sources?(address) && check_interval(to_string(address.hash)) do + fetch_sources(address) + end + + {:noreply, state} + end + + defp need_to_fetch_sources?(%Address{smart_contract: nil}), do: true + + defp need_to_fetch_sources?(%Address{hash: hash}) do + case Chain.address_hash_to_one_smart_contract(hash) do + nil -> + true + + _ -> + false + end + end + + defp check_interval(address_string) do + fetch_interval = + Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)[:fetch_interval] + + case :ets.lookup(@cache_name, address_string) do + [{_, datetime}] -> + datetime + |> DateTime.add(fetch_interval, :millisecond) + |> DateTime.compare(DateTime.utc_now()) != :gt + + _ -> + true + end + end + + def process_contract_source("SOLIDITY", source, address_hash) do + SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true) + end + + def process_contract_source("VYPER", source, address_hash) do + VyperPublisher.process_rust_verifier_response(source, address_hash, true) + end + + def process_contract_source("YUL", source, address_hash) do + SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true) + end + + def process_contract_source(_, _source, _address_hash), do: false +end diff --git a/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex new file mode 100644 index 000000000000..51656f766883 --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex @@ -0,0 +1,21 @@ +defmodule Explorer.SmartContract.EthBytecodeDBInterface do + @moduledoc """ + Adapter for interaction with https://github.com/blockscout/blockscout-rs/tree/main/eth-bytecode-db + """ + + def search_contract(%{"bytecode" => _, "bytecodeType" => _} = body) do + http_post_request(bytecode_search_sources_url(), body) + end + + def process_verifier_response(%{"sources" => [src | _]}) do + {:ok, src} + end + + def process_verifier_response(%{"sources" => []}) do + {:ok, nil} + end + + def bytecode_search_sources_url, do: "#{base_api_url()}" <> "/bytecodes/sources:search" + + use Explorer.SmartContract.RustVerifierInterfaceBehaviour +end diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 8de4c66ae5be..697327a75b9b 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -121,4 +121,14 @@ defmodule Explorer.SmartContract.Helper do |> Map.values() |> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end) end + + def contract_creation_input(address_hash) do + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + nil + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex index 2cc1a5772887..43c6f4befd28 100644 --- a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex @@ -2,145 +2,5 @@ defmodule Explorer.SmartContract.RustVerifierInterface do @moduledoc """ Adapter for contracts verification with https://github.com/blockscout/blockscout-rs/blob/main/smart-contract-verifier """ - alias Explorer.Utility.RustService - alias HTTPoison.Response - require Logger - - @post_timeout :timer.seconds(120) - @request_error_msg "Error while sending request to verification microservice" - - def verify_multi_part( - %{ - "bytecode" => _, - "bytecodeType" => _, - "compilerVersion" => _, - "sourceFiles" => _, - "evmVersion" => _, - "optimizationRuns" => _, - "libraries" => _ - } = body - ) do - http_post_request(multiple_files_verification_url(), body) - end - - def verify_standard_json_input( - %{ - "bytecode" => _, - "bytecodeType" => _, - "compilerVersion" => _, - "input" => _ - } = body - ) do - http_post_request(standard_json_input_verification_url(), body) - end - - def vyper_verify_multipart( - %{ - "bytecode" => _, - "bytecodeType" => _, - "compilerVersion" => _, - "sourceFiles" => _ - } = body - ) do - http_post_request(vyper_multiple_files_verification_url(), body) - end - - def http_post_request(url, body) do - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.post(url, Jason.encode!(normalize_creation_bytecode(body)), headers, recv_timeout: @post_timeout) do - {:ok, %Response{body: body, status_code: _}} -> - process_verifier_response(body) - - {:error, error} -> - old_truncate = Application.get_env(:logger, :truncate) - Logger.configure(truncate: :infinity) - - Logger.error(fn -> - [ - "Error while sending request to verification microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", - inspect(error, limit: :infinity, printable_limit: :infinity) - ] - end) - - Logger.configure(truncate: old_truncate) - {:error, @request_error_msg} - end - end - - def http_get_request(url) do - case HTTPoison.get(url) do - {:ok, %Response{body: body, status_code: 200}} -> - process_verifier_response(body) - - {:ok, %Response{body: body, status_code: _}} -> - {:error, body} - - {:error, error} -> - old_truncate = Application.get_env(:logger, :truncate) - Logger.configure(truncate: :infinity) - - Logger.error(fn -> - [ - "Error while sending request to verification microservice url: #{url}: ", - inspect(error, limit: :infinity, printable_limit: :infinity) - ] - end) - - Logger.configure(truncate: old_truncate) - {:error, @request_error_msg} - end - end - - def get_versions_list do - http_get_request(versions_list_url()) - end - - def vyper_get_versions_list do - http_get_request(vyper_versions_list_url()) - end - - def process_verifier_response(body) when is_binary(body) do - case Jason.decode(body) do - {:ok, decoded} -> - process_verifier_response(decoded) - - _ -> - {:error, body} - end - end - - def process_verifier_response(%{"status" => "SUCCESS", "source" => source}) do - {:ok, source} - end - - def process_verifier_response(%{"status" => "FAILURE", "message" => error}) do - {:error, error} - end - - def process_verifier_response(%{"compilerVersions" => versions}), do: {:ok, versions} - - def process_verifier_response(other), do: {:error, other} - - def normalize_creation_bytecode(%{"creation_bytecode" => ""} = map), do: Map.replace(map, "creation_bytecode", nil) - - def normalize_creation_bytecode(map), do: map - - def multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-multi-part" - - def vyper_multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/vyper/sources:verify-multi-part" - - def standard_json_input_verification_url, do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-standard-json" - - def versions_list_url, do: "#{base_api_url()}" <> "/verifier/solidity/versions" - - def vyper_versions_list_url, do: "#{base_api_url()}" <> "/verifier/vyper/versions" - - def base_api_url, do: "#{base_url()}" <> "/api/v2" - - def base_url do - RustService.base_url(__MODULE__) - end - - def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] + use Explorer.SmartContract.RustVerifierInterfaceBehaviour end diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex new file mode 100644 index 000000000000..4665de0d1cdb --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex @@ -0,0 +1,158 @@ +defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do + @moduledoc """ + This behaviour module was created in order to add possibility to extend the functionality of RustVerifierInterface + """ + defmacro __using__(_) do + quote([]) do + alias Explorer.Utility.RustService + alias HTTPoison.Response + require Logger + + @post_timeout :timer.seconds(120) + @request_error_msg "Error while sending request to verification microservice" + + def verify_multi_part( + %{ + "bytecode" => _, + "bytecodeType" => _, + "compilerVersion" => _, + "sourceFiles" => _, + "evmVersion" => _, + "optimizationRuns" => _, + "libraries" => _ + } = body, + address_hash + ) do + http_post_request(multiple_files_verification_url(), append_metadata(body, address_hash)) + end + + def verify_standard_json_input( + %{ + "bytecode" => _, + "bytecodeType" => _, + "compilerVersion" => _, + "input" => _ + } = body, + address_hash + ) do + http_post_request(standard_json_input_verification_url(), append_metadata(body, address_hash)) + end + + def vyper_verify_multipart( + %{ + "bytecode" => _, + "bytecodeType" => _, + "compilerVersion" => _, + "sourceFiles" => _ + } = body, + address_hash + ) do + http_post_request(vyper_multiple_files_verification_url(), append_metadata(body, address_hash)) + end + + def http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: _}} -> + process_verifier_response(body) + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to verification microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def http_get_request(url) do + case HTTPoison.get(url) do + {:ok, %Response{body: body, status_code: 200}} -> + process_verifier_response(body) + + {:ok, %Response{body: body, status_code: _}} -> + {:error, body} + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to verification microservice url: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def get_versions_list do + http_get_request(versions_list_url()) + end + + def vyper_get_versions_list do + http_get_request(vyper_versions_list_url()) + end + + def process_verifier_response(body) when is_binary(body) do + case Jason.decode(body) do + {:ok, decoded} -> + process_verifier_response(decoded) + + _ -> + {:error, body} + end + end + + def process_verifier_response(%{"status" => "SUCCESS", "source" => source}) do + {:ok, source} + end + + def process_verifier_response(%{"status" => "FAILURE", "message" => error}) do + {:error, error} + end + + def process_verifier_response(%{"compilerVersions" => versions}), do: {:ok, versions} + + def process_verifier_response(other), do: {:error, other} + + def multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-multi-part" + + def vyper_multiple_files_verification_url, do: "#{base_api_url()}" <> "/verifier/vyper/sources:verify-multi-part" + + def standard_json_input_verification_url, + do: "#{base_api_url()}" <> "/verifier/solidity/sources:verify-standard-json" + + def versions_list_url, do: "#{base_api_url()}" <> "/verifier/solidity/versions" + + def vyper_versions_list_url, do: "#{base_api_url()}" <> "/verifier/vyper/versions" + + def base_api_url, do: "#{base_url()}" <> "/api/v2" + + def base_url do + RustService.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour) + end + + def enabled?, do: Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:enabled] + + defp append_metadata(body, address_hash) when is_map(body) do + body + |> Map.put("metadata", %{ + "chainId" => Application.get_env(:block_scout_web, :chain_id), + "contractAddress" => to_string(address_hash) + }) + end + end + 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 2b436cc5c357..a41847ad3c82 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -8,7 +8,8 @@ defmodule Explorer.SmartContract.Solidity.Verifier do then Verified. """ - import Explorer.SmartContract.Helper, only: [cast_libraries: 1, prepare_bytecode_for_microservice: 3] + import Explorer.SmartContract.Helper, + only: [cast_libraries: 1, prepare_bytecode_for_microservice: 3, contract_creation_input: 1] alias ABI.{FunctionSelector, TypeDecoder} alias Explorer.Chain @@ -39,14 +40,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do defp evaluate_authenticity_inner(true, address_hash, params) 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 - - _ -> - nil - end + creation_tx_input = contract_creation_input(address_hash) %{} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) @@ -58,7 +52,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part() + |> RustVerifierInterface.verify_multi_part(address_hash) end defp evaluate_authenticity_inner(false, address_hash, params) do @@ -130,19 +124,12 @@ defmodule Explorer.SmartContract.Solidity.Verifier do def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) 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 - - _ -> - nil - end + creation_tx_input = contract_creation_input(address_hash) %{"compilerVersion" => params["compiler_version"]} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> Map.put("input", json_input) - |> RustVerifierInterface.verify_standard_json_input() + |> RustVerifierInterface.verify_standard_json_input(address_hash) end def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do @@ -152,14 +139,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do def evaluate_authenticity_via_multi_part_files(address_hash, params, files) 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 - - _ -> - nil - end + creation_tx_input = contract_creation_input(address_hash) %{} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) @@ -168,7 +148,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part() + |> RustVerifierInterface.verify_multi_part(address_hash) end defp verify(address_hash, params, json_input) do diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex index 5d73a203c9f3..22613046682a 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex @@ -15,29 +15,16 @@ defmodule Explorer.SmartContract.Vyper.Publisher do { :ok, %{ - "abi" => abi_string, - "compilerVersion" => compiler_version, - "constructorArguments" => constructor_arguments, - "contractName" => contract_name, - "fileName" => file_name, - "sourceFiles" => sources, - "compilerSettings" => compiler_settings_string - } + "abi" => _abi_string, + "compilerVersion" => _compiler_version, + "constructorArguments" => _constructor_arguments, + "contractName" => _contract_name, + "fileName" => _file_name, + "sourceFiles" => _sources, + "compilerSettings" => _compiler_settings_string + } = source } -> - %{^file_name => contract_source_code} = sources - - compiler_settings = Jason.decode!(compiler_settings_string) - - prepared_params = - %{} - |> Map.put("compiler_version", compiler_version) - |> Map.put("constructor_arguments", constructor_arguments) - |> Map.put("contract_source_code", contract_source_code) - |> Map.put("evm_version", compiler_settings["evmVersion"] || "istanbul") - |> Map.put("external_libraries", cast_libraries(compiler_settings["libraries"] || %{})) - |> Map.put("name", contract_name) - - publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) + process_rust_verifier_response(source, address_hash, false) {:ok, %{abi: abi}} -> publish_smart_contract(address_hash, params, abi) @@ -55,36 +42,16 @@ defmodule Explorer.SmartContract.Vyper.Publisher do { :ok, %{ - "abi" => abi_string, - "compilerVersion" => compiler_version, - "constructorArguments" => constructor_arguments, - "contractName" => contract_name, - "fileName" => file_name, - "sourceFiles" => sources, - "compilerSettings" => compiler_settings_string - } + "abi" => _abi_string, + "compilerVersion" => _compiler_version, + "constructorArguments" => _constructor_arguments, + "contractName" => _contract_name, + "fileName" => _file_name, + "sourceFiles" => _sources, + "compilerSettings" => _compiler_settings_string + } = source } -> - 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 - - compiler_settings = Jason.decode!(compiler_settings_string) - - prepared_params = - %{} - |> Map.put("compiler_version", compiler_version) - |> Map.put("constructor_arguments", constructor_arguments) - |> Map.put("contract_source_code", contract_source_code) - |> Map.put("external_libraries", cast_libraries(compiler_settings["libraries"] || %{})) - |> Map.put("name", contract_name) - |> Map.put("file_path", file_name) - |> Map.put("secondary_sources", secondary_sources) - |> Map.put("evm_version", compiler_settings["evmVersion"] || "default") - - publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) + process_rust_verifier_response(source, address_hash, true) {:ok, %{abi: abi}} -> publish_smart_contract(address_hash, params, abi) @@ -97,6 +64,42 @@ defmodule Explorer.SmartContract.Vyper.Publisher do end end + def process_rust_verifier_response( + %{ + "abi" => abi_string, + "compilerVersion" => compiler_version, + "constructorArguments" => constructor_arguments, + "contractName" => contract_name, + "fileName" => file_name, + "sourceFiles" => sources, + "compilerSettings" => compiler_settings_string + }, + address_hash, + save_file_path? + ) do + 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 + + compiler_settings = Jason.decode!(compiler_settings_string) + + prepared_params = + %{} + |> Map.put("compiler_version", compiler_version) + |> Map.put("constructor_arguments", constructor_arguments) + |> Map.put("contract_source_code", contract_source_code) + |> Map.put("external_libraries", cast_libraries(compiler_settings["libraries"] || %{})) + |> Map.put("name", contract_name) + |> Map.put("file_path", if(save_file_path?, do: file_name)) + |> Map.put("secondary_sources", secondary_sources) + |> Map.put("evm_version", compiler_settings["evmVersion"] || "istanbul") + + publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) + end + def publish_smart_contract(address_hash, params, abi) do attrs = address_hash |> attributes(params, abi) diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex index 2e38b7d1420c..3042d0e5eeef 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex @@ -12,7 +12,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do alias Explorer.Chain alias Explorer.SmartContract.Vyper.CodeCompiler alias Explorer.SmartContract.RustVerifierInterface - import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3] + import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1] def evaluate_authenticity(_, %{"contract_source_code" => ""}), do: {:error, :contract_source_code} @@ -36,16 +36,9 @@ defmodule Explorer.SmartContract.Vyper.Verifier 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 + creation_tx_input = contract_creation_input(address_hash) - _ -> - nil - end - - vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], files) + vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], files, address_hash) end rescue exception -> @@ -61,18 +54,18 @@ defmodule Explorer.SmartContract.Vyper.Verifier do defp evaluate_authenticity_inner(true, address_hash, params) 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 - - _ -> - nil - end - - vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], %{ - "#{params["name"]}.vy" => params["contract_source_code"] - }) + creation_tx_input = contract_creation_input(address_hash) + + vyper_verify_multipart( + params, + creation_tx_input, + deployed_bytecode, + params["evm_version"], + %{ + "#{params["name"]}.vy" => params["contract_source_code"] + }, + address_hash + ) end defp evaluate_authenticity_inner(false, address_hash, params) do @@ -99,20 +92,14 @@ defmodule Explorer.SmartContract.Vyper.Verifier do defp compare_bytecodes({:error, _}, _, _), do: {:error, :compilation} - # credo:disable-for-next-line /Complexity/ defp compare_bytecodes( {:ok, %{"abi" => abi, "bytecode" => bytecode}}, address_hash, arguments_data ) do blockchain_bytecode = - case Chain.smart_contract_creation_tx_bytecode(address_hash) do - %{init: init, created_contract_code: _created_contract_code} -> - init - - _ -> - nil - end + address_hash + |> contract_creation_input() |> String.trim() if String.trim(bytecode <> arguments_data) == blockchain_bytecode do @@ -122,12 +109,12 @@ defmodule Explorer.SmartContract.Vyper.Verifier do end end - defp vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, evm_version, files) do + defp vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, evm_version, files, address_hash) do %{} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> Map.put("evmVersion", evm_version || "istanbul") |> Map.put("sourceFiles", files) |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.vyper_verify_multipart() + |> RustVerifierInterface.vyper_verify_multipart(address_hash) end end diff --git a/config/runtime.exs b/config/runtime.exs index f89cdf27cedb..5e9290a801b6 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -318,9 +318,11 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, chain_id: System.get_env("CHAIN_ID"), repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" -config :explorer, Explorer.SmartContract.RustVerifierInterface, +config :explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: System.get_env("RUST_VERIFICATION_SERVICE_URL"), - enabled: ConfigHelper.parse_bool_env_var("ENABLE_RUST_VERIFICATION_SERVICE") + enabled: ConfigHelper.parse_bool_env_var("ENABLE_RUST_VERIFICATION_SERVICE"), + # or "eth_bytecode_db" + type: System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") config :explorer, Explorer.Visualize.Sol2uml, service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"), @@ -362,6 +364,10 @@ config :explorer, :datadog, port: ConfigHelper.parse_integer_env_var("DATADOG_PO config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, max_cache_size: ConfigHelper.parse_integer_env_var("INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE", 100_000) +config :explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, + fetch_interval: + ConfigHelper.parse_time_env_var("MICROSERVICE_MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS", "10m") + ############### ### Indexer ### ############### diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 324a8223b8fd..d1a54a8804d3 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -156,6 +156,8 @@ API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000 FETCH_REWARDS_WAY=trace_block ENABLE_RUST_VERIFICATION_SERVICE=true RUST_VERIFICATION_SERVICE_URL=http://smart-contract-verifier:8050/ +MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=10m +MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier VISUALIZE_SOL2UML_ENABLED=true VISUALIZE_SOL2UML_SERVICE_URL=http://visualizer:8050/ SIG_PROVIDER_ENABLED=true @@ -180,4 +182,4 @@ ACCOUNT_REDIS_URL=redis://redis_db:6379 # MIXPANEL_TOKEN= # MIXPANEL_URL= # AMPLITUDE_API_KEY= -# AMPLITUDE_URL= \ No newline at end of file +# AMPLITUDE_URL= diff --git a/docker/Makefile b/docker/Makefile index 2c5a59c23143..1076f2e7d2c8 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -564,6 +564,12 @@ endif ifdef RUST_VERIFICATION_SERVICE_URL BLOCKSCOUT_CONTAINER_PARAMS += -e 'RUST_VERIFICATION_SERVICE_URL=$(RUST_VERIFICATION_SERVICE_URL)' endif +ifdef MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=$(MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS)' +endif +ifdef MICROSERVICE_SC_VERIFIER_TYPE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MICROSERVICE_SC_VERIFIER_TYPE=$(MICROSERVICE_SC_VERIFIER_TYPE)' +endif ifdef ACCOUNT_ENABLED BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_ENABLED=$(ACCOUNT_ENABLED)' endif