Integrate Eth Bytecode DB (#7187)

* Add Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand

* Add smart_contract_was_verified broadcast

* Changelog

* Rollback rename

* Refactoring

* Fix warning on rendering contracts without constructor args

* Add metadata to the verification request to microservice

* Add ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS

* Add MICROSERVICE_ prefix to MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS; Add MICROSERVICE_SC_VERIFIER_TYPE

* Fix tests

* Update runtime.exs

* Update common-blockscout.env

---------

Co-authored-by: Victor Baranov <baranov.viktor.27@gmail.com>
pull/7245/head
nikitosing 2 years ago committed by GitHub
parent 46f77791e3
commit 3633b7d273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      .github/workflows/config.yml
  2. 1
      CHANGELOG.md
  3. 4
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  4. 1
      apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
  5. 5
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  6. 113
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  7. 17
      apps/block_scout_web/test/support/fixture/smart_contract/eth_bytecode_db_search_response.json
  8. 13
      apps/explorer/lib/explorer/application.ex
  9. 13
      apps/explorer/lib/explorer/chain.ex
  10. 2
      apps/explorer/lib/explorer/chain/events/publisher.ex
  11. 2
      apps/explorer/lib/explorer/chain/events/subscriber.ex
  12. 112
      apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex
  13. 21
      apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex
  14. 10
      apps/explorer/lib/explorer/smart_contract/helper.ex
  15. 142
      apps/explorer/lib/explorer/smart_contract/rust_verifier_interface.ex
  16. 158
      apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex
  17. 36
      apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
  18. 105
      apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
  19. 51
      apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex
  20. 10
      config/runtime.exs
  21. 4
      docker-compose/envs/common-blockscout.env
  22. 6
      docker/Makefile

@ -40,7 +40,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-
@ -100,7 +100,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -124,7 +124,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -147,7 +147,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -187,7 +187,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -213,7 +213,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -242,7 +242,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -287,7 +287,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -333,7 +333,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -390,7 +390,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -444,7 +444,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -509,7 +509,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -573,7 +573,7 @@ jobs:
path: | path: |
deps deps
_build _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: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"

@ -4,6 +4,7 @@
### Features ### 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 - [#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 - [#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

@ -225,6 +225,10 @@ defmodule BlockScoutWeb.Notifier do
Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{}) Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{})
end 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 handle_event(_), do: nil
def fetch_compiler_version(compiler) do def fetch_compiler_version(compiler) do

@ -30,6 +30,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
Subscriber.to(:contract_verification_result, :on_demand) Subscriber.to(:contract_verification_result, :on_demand)
Subscriber.to(:token_total_supply, :on_demand) Subscriber.to(:token_total_supply, :on_demand)
Subscriber.to(:changed_bytecode, :on_demand) Subscriber.to(:changed_bytecode, :on_demand)
Subscriber.to(:smart_contract_was_verified, :on_demand)
# Does not come from the indexer # Does not come from the indexer
Subscriber.to(:exchange_rate) Subscriber.to(:exchange_rate)
Subscriber.to(:transaction_stats) Subscriber.to(:transaction_stats)

@ -216,7 +216,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
} }
end 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) 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) input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1)
@ -242,6 +243,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
nil nil
end end
def format_constructor_arguments(_abi, _constructor_arguments), do: nil
defp prepare_smart_contract_for_list(%SmartContract{} = smart_contract) do defp prepare_smart_contract_for_list(%SmartContract{} = smart_contract) do
token = smart_contract.address.token token = smart_contract.address.token

@ -1,11 +1,13 @@
defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
use BlockScoutWeb.ChannelCase, async: false
import Mox import Mox
alias BlockScoutWeb.AddressContractView alias BlockScoutWeb.AddressContractView
alias BlockScoutWeb.Models.UserFromAuth alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.Chain.{Address, SmartContract} alias Explorer.Chain.{Address, SmartContract}
alias Plug.Conn
setup :set_mox_from_context setup :set_mox_from_context
@ -295,6 +297,117 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert correct_response == response assert correct_response == response
end 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 end
describe "/smart-contracts/{address_hash}/methods-read" do describe "/smart-contracts/{address_hash}/methods-read" do

@ -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"
}
]
}

@ -111,7 +111,8 @@ defmodule Explorer.Application do
configure(MinMissingBlockNumber), configure(MinMissingBlockNumber),
configure(TokenTransferTokenIdMigration.Supervisor), configure(TokenTransferTokenIdMigration.Supervisor),
configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand), configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand),
configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand),
sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)
] ]
|> List.flatten() |> List.flatten()
end end
@ -128,6 +129,16 @@ defmodule Explorer.Application do
end end
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 defp datadog_port do
Application.get_env(:explorer, :datadog)[:port] Application.get_env(:explorer, :datadog)[:port]
end end

@ -80,7 +80,7 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Cache.Block, as: BlockCache alias Explorer.Chain.Cache.Block, as: BlockCache
alias Explorer.Chain.Cache.Helper, as: CacheHelper 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.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type} alias Explorer.Chain.InternalTransaction.{CallType, Type}
@ -1950,8 +1950,11 @@ defmodule Explorer.Chain do
%{smart_contract: smart_contract} -> %{smart_contract: smart_contract} ->
if smart_contract do if smart_contract do
CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract) CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract)
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract)
address_result address_result
else else
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil)
address_verified_twin_contract = address_verified_twin_contract =
get_minimal_proxy_template(hash, options) || get_minimal_proxy_template(hash, options) ||
get_address_verified_twin_contract(hash, options).verified_contract get_address_verified_twin_contract(hash, options).verified_contract
@ -1960,6 +1963,7 @@ defmodule Explorer.Chain do
end end
_ -> _ ->
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil)
address_result address_result
end end
@ -4428,6 +4432,13 @@ defmodule Explorer.Chain do
end end
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 @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 def address_hash_to_smart_contract_without_twin(address_hash, options) do
query = query =

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do
Publishes events related to the Chain context. 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 def broadcast(_data, false), do: :ok

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do
Subscribes to events related to the Chain context. 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 @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a

@ -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

@ -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

@ -121,4 +121,14 @@ defmodule Explorer.SmartContract.Helper do
|> Map.values() |> Map.values()
|> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end) |> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end)
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 end

@ -2,145 +2,5 @@ defmodule Explorer.SmartContract.RustVerifierInterface do
@moduledoc """ @moduledoc """
Adapter for contracts verification with https://github.com/blockscout/blockscout-rs/blob/main/smart-contract-verifier Adapter for contracts verification with https://github.com/blockscout/blockscout-rs/blob/main/smart-contract-verifier
""" """
alias Explorer.Utility.RustService use Explorer.SmartContract.RustVerifierInterfaceBehaviour
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]
end end

@ -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

@ -8,7 +8,8 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
then Verified. 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 ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain alias Explorer.Chain
@ -39,14 +40,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
defp evaluate_authenticity_inner(true, address_hash, params) do defp evaluate_authenticity_inner(true, address_hash, params) do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input = creation_tx_input = contract_creation_input(address_hash)
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
init
_ ->
nil
end
%{} %{}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> 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("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"]))
|> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("evmVersion", Map.get(params, "evm_version", "default"))
|> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("compilerVersion", params["compiler_version"])
|> RustVerifierInterface.verify_multi_part() |> RustVerifierInterface.verify_multi_part(address_hash)
end end
defp evaluate_authenticity_inner(false, address_hash, params) do 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 def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input = creation_tx_input = contract_creation_input(address_hash)
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
init
_ ->
nil
end
%{"compilerVersion" => params["compiler_version"]} %{"compilerVersion" => params["compiler_version"]}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
|> Map.put("input", json_input) |> Map.put("input", json_input)
|> RustVerifierInterface.verify_standard_json_input() |> RustVerifierInterface.verify_standard_json_input(address_hash)
end end
def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do 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 def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input = creation_tx_input = contract_creation_input(address_hash)
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
init
_ ->
nil
end
%{} %{}
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> 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("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"]))
|> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("evmVersion", Map.get(params, "evm_version", "default"))
|> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("compilerVersion", params["compiler_version"])
|> RustVerifierInterface.verify_multi_part() |> RustVerifierInterface.verify_multi_part(address_hash)
end end
defp verify(address_hash, params, json_input) do defp verify(address_hash, params, json_input) do

@ -15,29 +15,16 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
{ {
:ok, :ok,
%{ %{
"abi" => abi_string, "abi" => _abi_string,
"compilerVersion" => compiler_version, "compilerVersion" => _compiler_version,
"constructorArguments" => constructor_arguments, "constructorArguments" => _constructor_arguments,
"contractName" => contract_name, "contractName" => _contract_name,
"fileName" => file_name, "fileName" => _file_name,
"sourceFiles" => sources, "sourceFiles" => _sources,
"compilerSettings" => compiler_settings_string "compilerSettings" => _compiler_settings_string
} } = source
} -> } ->
%{^file_name => contract_source_code} = sources process_rust_verifier_response(source, address_hash, false)
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))
{:ok, %{abi: abi}} -> {:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi) publish_smart_contract(address_hash, params, abi)
@ -55,36 +42,16 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
{ {
:ok, :ok,
%{ %{
"abi" => abi_string, "abi" => _abi_string,
"compilerVersion" => compiler_version, "compilerVersion" => _compiler_version,
"constructorArguments" => constructor_arguments, "constructorArguments" => _constructor_arguments,
"contractName" => contract_name, "contractName" => _contract_name,
"fileName" => file_name, "fileName" => _file_name,
"sourceFiles" => sources, "sourceFiles" => _sources,
"compilerSettings" => compiler_settings_string "compilerSettings" => _compiler_settings_string
} } = source
} -> } ->
secondary_sources = process_rust_verifier_response(source, address_hash, true)
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))
{:ok, %{abi: abi}} -> {:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi) publish_smart_contract(address_hash, params, abi)
@ -97,6 +64,42 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
end end
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 def publish_smart_contract(address_hash, params, abi) do
attrs = address_hash |> attributes(params, abi) attrs = address_hash |> attributes(params, abi)

@ -12,7 +12,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
alias Explorer.Chain alias Explorer.Chain
alias Explorer.SmartContract.Vyper.CodeCompiler alias Explorer.SmartContract.Vyper.CodeCompiler
alias Explorer.SmartContract.RustVerifierInterface 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" => ""}), def evaluate_authenticity(_, %{"contract_source_code" => ""}),
do: {:error, :contract_source_code} do: {:error, :contract_source_code}
@ -36,16 +36,9 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
if RustVerifierInterface.enabled?() do if RustVerifierInterface.enabled?() do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input = creation_tx_input = contract_creation_input(address_hash)
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} ->
init
_ -> vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], files, address_hash)
nil
end
vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], files)
end end
rescue rescue
exception -> exception ->
@ -61,18 +54,18 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
defp evaluate_authenticity_inner(true, address_hash, params) do defp evaluate_authenticity_inner(true, address_hash, params) do
deployed_bytecode = Chain.smart_contract_bytecode(address_hash) deployed_bytecode = Chain.smart_contract_bytecode(address_hash)
creation_tx_input = creation_tx_input = contract_creation_input(address_hash)
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
%{init: init, created_contract_code: _created_contract_code} -> vyper_verify_multipart(
init params,
creation_tx_input,
_ -> deployed_bytecode,
nil params["evm_version"],
end %{
"#{params["name"]}.vy" => params["contract_source_code"]
vyper_verify_multipart(params, creation_tx_input, deployed_bytecode, params["evm_version"], %{ },
"#{params["name"]}.vy" => params["contract_source_code"] address_hash
}) )
end end
defp evaluate_authenticity_inner(false, address_hash, params) do 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} defp compare_bytecodes({:error, _}, _, _), do: {:error, :compilation}
# credo:disable-for-next-line /Complexity/
defp compare_bytecodes( defp compare_bytecodes(
{:ok, %{"abi" => abi, "bytecode" => bytecode}}, {:ok, %{"abi" => abi, "bytecode" => bytecode}},
address_hash, address_hash,
arguments_data arguments_data
) do ) do
blockchain_bytecode = blockchain_bytecode =
case Chain.smart_contract_creation_tx_bytecode(address_hash) do address_hash
%{init: init, created_contract_code: _created_contract_code} -> |> contract_creation_input()
init
_ ->
nil
end
|> String.trim() |> String.trim()
if String.trim(bytecode <> arguments_data) == blockchain_bytecode do if String.trim(bytecode <> arguments_data) == blockchain_bytecode do
@ -122,12 +109,12 @@ defmodule Explorer.SmartContract.Vyper.Verifier do
end end
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) |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
|> Map.put("evmVersion", evm_version || "istanbul") |> Map.put("evmVersion", evm_version || "istanbul")
|> Map.put("sourceFiles", files) |> Map.put("sourceFiles", files)
|> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("compilerVersion", params["compiler_version"])
|> RustVerifierInterface.vyper_verify_multipart() |> RustVerifierInterface.vyper_verify_multipart(address_hash)
end end
end end

@ -318,9 +318,11 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify,
chain_id: System.get_env("CHAIN_ID"), chain_id: System.get_env("CHAIN_ID"),
repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" 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"), 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, config :explorer, Explorer.Visualize.Sol2uml,
service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"), 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, config :explorer, Explorer.Chain.Cache.TransactionActionTokensData,
max_cache_size: ConfigHelper.parse_integer_env_var("INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE", 100_000) 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 ### ### Indexer ###
############### ###############

@ -156,6 +156,8 @@ API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000
FETCH_REWARDS_WAY=trace_block FETCH_REWARDS_WAY=trace_block
ENABLE_RUST_VERIFICATION_SERVICE=true ENABLE_RUST_VERIFICATION_SERVICE=true
RUST_VERIFICATION_SERVICE_URL=http://smart-contract-verifier:8050/ 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_ENABLED=true
VISUALIZE_SOL2UML_SERVICE_URL=http://visualizer:8050/ VISUALIZE_SOL2UML_SERVICE_URL=http://visualizer:8050/
SIG_PROVIDER_ENABLED=true SIG_PROVIDER_ENABLED=true
@ -180,4 +182,4 @@ ACCOUNT_REDIS_URL=redis://redis_db:6379
# MIXPANEL_TOKEN= # MIXPANEL_TOKEN=
# MIXPANEL_URL= # MIXPANEL_URL=
# AMPLITUDE_API_KEY= # AMPLITUDE_API_KEY=
# AMPLITUDE_URL= # AMPLITUDE_URL=

@ -564,6 +564,12 @@ endif
ifdef RUST_VERIFICATION_SERVICE_URL ifdef RUST_VERIFICATION_SERVICE_URL
BLOCKSCOUT_CONTAINER_PARAMS += -e 'RUST_VERIFICATION_SERVICE_URL=$(RUST_VERIFICATION_SERVICE_URL)' BLOCKSCOUT_CONTAINER_PARAMS += -e 'RUST_VERIFICATION_SERVICE_URL=$(RUST_VERIFICATION_SERVICE_URL)'
endif 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 ifdef ACCOUNT_ENABLED
BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_ENABLED=$(ACCOUNT_ENABLED)' BLOCKSCOUT_CONTAINER_PARAMS += -e 'ACCOUNT_ENABLED=$(ACCOUNT_ENABLED)'
endif endif

Loading…
Cancel
Save