feat: Support smart-contract verification in zkSync (#10500)

* db changes for zksync smart-contract verification support

* Update SmartContract model

* Pass zk_compiler_version value in API v1/v2 endpoints

* Extend sc verification config with config for zk compilers

* Fix constructor arguments saving

* Enhance api/v2/smart-contracts/verification/config for zkSync

* Process review comments

* creationMatch -> runtimeMatch

* Fix merging conflicts

* Finishing touches
pull/10604/head
Victor Baranov 3 months ago committed by GitHub
parent 5d98e8ba33
commit c4a13a726e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/config.yml
  2. 10
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  3. 77
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
  4. 65
      apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex
  5. 15
      apps/block_scout_web/lib/block_scout_web/routers/smart_contracts_api_v2_router.ex
  6. 37
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  7. 55
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  8. 78
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  9. 1549
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  10. 570
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/verification_controller_test.exs
  11. 246
      apps/explorer/lib/explorer/chain/smart_contract.ex
  12. 1
      apps/explorer/lib/explorer/chain_spec/genesis_data.ex
  13. 53
      apps/explorer/lib/explorer/smart_contract/compiler_version.ex
  14. 29
      apps/explorer/lib/explorer/smart_contract/helper.ex
  15. 60
      apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex
  16. 161
      apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
  17. 26
      apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
  18. 10
      apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
  19. 10
      apps/explorer/priv/zk_sync/migrations/20240716095237_add_zk_compiler_version_to_smart_contracts.exs
  20. 410
      apps/explorer/test/explorer/smart_contract/solidity/publisher_test.exs
  21. 2364
      apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs
  22. 114
      apps/explorer/test/explorer/smart_contract/vyper/publisher_test.exs
  23. 8
      apps/explorer/test/support/factory.ex

@ -49,7 +49,7 @@ jobs:
// Add/remove CI matrix chain types here
const defaultChainTypes = ["default"];
const chainTypes = ["ethereum", "polygon_zkevm", "rsk", "stability", "filecoin", "optimism", "arbitrum", "celo", "zetachain"];
const chainTypes = ["ethereum", "polygon_zkevm", "rsk", "stability", "filecoin", "optimism", "arbitrum", "celo", "zetachain", "zksync", "shibarium"];
const extraChainTypes = ["suave", "polygon_edge"];
// Chain type matrix we use in master branch

@ -17,6 +17,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
alias Explorer.ThirdPartyIntegrations.Sourcify
import BlockScoutWeb.API.V2.AddressController, only: [validate_address: 2, validate_address: 3]
if Application.compile_env(:explorer, :chain_type) == :zksync do
@optimization_runs "0"
else
@optimization_runs 200
end
@smth_went_wrong "Something went wrong while publishing the contract"
@verified "Smart-contract already verified."
@invalid_address "Invalid address hash"
@ -661,7 +667,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
{:ok, Map.put(opts, "optimization_runs", runs_int)}
_ ->
{:ok, Map.put(opts, "optimization_runs", 200)}
{:ok, Map.put(opts, "optimization_runs", @optimization_runs)}
end
end
@ -670,7 +676,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end
defp parse_optimization_runs({:ok, opts}) do
{:ok, Map.put(opts, "optimization_runs", 200)}
{:ok, Map.put(opts, "optimization_runs", @optimization_runs)}
end
defp parse_optimization_runs(other), do: other

@ -18,12 +18,43 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
@api_true [api?: true]
@sc_verification_started "Smart-contract verification started"
@zk_optimization_modes ["0", "1", "2", "3", "s", "z"]
if Application.compile_env(:explorer, :chain_type) == :zksync do
@optimization_runs "0"
else
@optimization_runs 200
end
def config(conn, _params) do
solidity_compiler_versions = CompilerVersion.fetch_version_list(:solc)
vyper_compiler_versions = CompilerVersion.fetch_version_list(:vyper)
verification_options =
verification_options = get_verification_options()
base_config =
%{
solidity_evm_versions: CodeCompiler.evm_versions(:solidity),
solidity_compiler_versions: solidity_compiler_versions,
vyper_compiler_versions: vyper_compiler_versions,
verification_options: verification_options,
vyper_evm_versions: CodeCompiler.evm_versions(:vyper),
is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?(),
license_types: Enum.into(SmartContract.license_types_enum(), %{})
}
config =
base_config
|> maybe_add_zk_options()
conn
|> json(config)
end
defp get_verification_options do
if Application.get_env(:explorer, :chain_type) == :zksync do
["standard-input"]
else
["flattened-code", "standard-input", "vyper-code"]
|> (&if(Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled],
do: ["sourcify" | &1],
@ -33,17 +64,19 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
do: ["multi-part", "vyper-multi-part", "vyper-standard-input"] ++ &1,
else: &1
)).()
end
end
conn
|> json(%{
solidity_evm_versions: CodeCompiler.evm_versions(:solidity),
solidity_compiler_versions: solidity_compiler_versions,
vyper_compiler_versions: vyper_compiler_versions,
verification_options: verification_options,
vyper_evm_versions: CodeCompiler.evm_versions(:vyper),
is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?(),
license_types: Enum.into(SmartContract.license_types_enum(), %{})
})
defp maybe_add_zk_options(config) do
if Application.get_env(:explorer, :chain_type) == :zksync do
zk_compiler_versions = CompilerVersion.fetch_version_list(:zk)
config
|> Map.put(:zk_compiler_versions, zk_compiler_versions)
|> Map.put(:zk_optimization_modes, @zk_optimization_modes)
else
config
end
end
def verification_via_flattened_code(
@ -62,7 +95,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
}
|> Map.put("optimization", Map.get(params, "is_optimization_enabled", false))
|> (&if(params |> Map.get("is_optimization_enabled", false) |> parse_boolean(),
do: Map.put(&1, "optimization_runs", Map.get(params, "optimization_runs", 200)),
do: Map.put(&1, "optimization_runs", Map.get(params, "optimization_runs", @optimization_runs)),
else: &1
)).()
|> Map.put("evm_version", Map.get(params, "evm_version", "default"))
@ -89,15 +122,27 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
Logger.info("API v2 smart-contract #{address_hash_string} verification via standard json input")
with {:json_input, json_input} <- validate_params_standard_json_input(params) do
constructor_arguments =
if Application.get_env(:explorer, :chain_type) == :zksync do
zksync_get_constructor_arguments(address_hash_string)
else
Map.get(params, "constructor_args", "")
end
verification_params =
%{
"address_hash" => String.downcase(address_hash_string),
"compiler_version" => compiler_version
}
|> Map.put("autodetect_constructor_args", Map.get(params, "autodetect_constructor_args", true))
|> Map.put("constructor_arguments", Map.get(params, "constructor_args", ""))
#
|> Map.put("constructor_arguments", constructor_arguments)
|> Map.put("name", Map.get(params, "contract_name", ""))
|> Map.put("license_type", Map.get(params, "license_type"))
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: Map.put(&1, "zk_compiler_version", Map.get(params, "zk_compiler_version")),
else: &1
)).()
log_sc_verification_started(address_hash_string)
Que.add(SolidityPublisherWorker, {"json_api_v2", verification_params, json_input})
@ -150,7 +195,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
}
|> Map.put("optimization", Map.get(params, "is_optimization_enabled", false))
|> (&if(params |> Map.get("is_optimization_enabled", false) |> parse_boolean(),
do: Map.put(&1, "optimization_runs", Map.get(params, "optimization_runs", 200)),
do: Map.put(&1, "optimization_runs", Map.get(params, "optimization_runs", @optimization_runs)),
else: &1
)).()
|> Map.put("evm_version", Map.get(params, "evm_version", "default"))
@ -275,6 +320,10 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
end
end
defp zksync_get_constructor_arguments(address_hash_string) do
Chain.contract_creation_input_data(address_hash_string)
end
# sobelow_skip ["Traversal.FileModule"]
defp validate_params_standard_json_input(%{"files" => files} = params) do
with :validated <- validate_address(params),

@ -1,6 +1,49 @@
defmodule BlockScoutWeb.GraphQL.Schema.SmartContracts do
@moduledoc false
case Application.compile_env(:explorer, :chain_type) do
:zksync ->
@chain_type_fields quote(
do: [
field(:optimization_runs, :string),
field(:zk_compiler_version, :string)
]
)
_ ->
@chain_type_fields quote(do: [field(:optimization_runs, :integer)])
end
defmacro generate do
quote do
object :smart_contract do
field(:name, :string)
field(:compiler_version, :string)
field(:optimization, :boolean)
field(:contract_source_code, :string)
field(:abi, :json)
field(:address_hash, :address_hash)
field(:constructor_arguments, :string)
field(:evm_version, :string)
field(:external_libraries, :json)
field(:verified_via_sourcify, :boolean)
field(:partially_verified, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
field(:is_changed_bytecode, :boolean)
field(:compiler_settings, :json)
field(:verified_via_eth_bytecode_db, :boolean)
unquote_splicing(@chain_type_fields)
end
end
end
end
defmodule BlockScoutWeb.GraphQL.Schema.Types do
@moduledoc false
require BlockScoutWeb.GraphQL.Schema.SmartContracts
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern
@ -11,6 +54,8 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do
Transaction
}
alias BlockScoutWeb.GraphQL.Schema.SmartContracts, as: SmartContractsSchema
import_types(Absinthe.Type.Custom)
import_types(BlockScoutWeb.GraphQL.Schema.Scalars)
@ -100,25 +145,7 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do
blockchain."
http://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html
"""
object :smart_contract do
field(:name, :string)
field(:compiler_version, :string)
field(:optimization, :boolean)
field(:contract_source_code, :string)
field(:abi, :json)
field(:address_hash, :address_hash)
field(:constructor_arguments, :string)
field(:optimization_runs, :integer)
field(:evm_version, :string)
field(:external_libraries, :json)
field(:verified_via_sourcify, :boolean)
field(:partially_verified, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
field(:is_changed_bytecode, :boolean)
field(:compiler_settings, :json)
field(:verified_via_eth_bytecode_db, :boolean)
end
SmartContractsSchema.generate()
@desc """
Represents a token transfer between addresses.

@ -69,12 +69,15 @@ defmodule BlockScoutWeb.Routers.SmartContractsApiV2Router do
scope "/:address_hash/verification/via", as: :api_v2 do
pipe_through(:api_v2_no_forgery_protect)
post("/flattened-code", V2.VerificationController, :verification_via_flattened_code)
post("/standard-input", V2.VerificationController, :verification_via_standard_input)
post("/sourcify", V2.VerificationController, :verification_via_sourcify)
post("/multi-part", V2.VerificationController, :verification_via_multi_part)
post("/vyper-code", V2.VerificationController, :verification_via_vyper_code)
post("/vyper-multi-part", V2.VerificationController, :verification_via_vyper_multipart)
post("/vyper-standard-input", V2.VerificationController, :verification_via_vyper_standard_input)
if Application.compile_env(:explorer, :chain_type) !== :zksync do
post("/flattened-code", V2.VerificationController, :verification_via_flattened_code)
post("/sourcify", V2.VerificationController, :verification_via_sourcify)
post("/multi-part", V2.VerificationController, :verification_via_multi_part)
post("/vyper-code", V2.VerificationController, :verification_via_vyper_code)
post("/vyper-multi-part", V2.VerificationController, :verification_via_vyper_multipart)
post("/vyper-standard-input", V2.VerificationController, :verification_via_vyper_standard_input)
end
end
end

@ -174,6 +174,16 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
|> Map.put_new(:EVMVersion, Map.get(contract, :evm_version, ""))
|> Map.put_new(:FileName, Map.get(contract, :file_path, "") || "")
|> insert_additional_sources(address)
|> add_zksync_info(contract)
end
end
defp add_zksync_info(smart_contract_info, contract) do
if Application.get_env(:explorer, :chain_type) == :zksync do
smart_contract_info
|> Map.put_new(:ZkCompilerVersion, Map.get(contract, :zk_compiler_version, ""))
else
smart_contract_info
end
end
@ -216,13 +226,26 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
hash: hash,
smart_contract: %SmartContract{} = contract
}) do
%{
"Address" => to_string(hash),
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
smart_contract_info =
%{
"Address" => to_string(hash),
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
smart_contract_info
|> merge_zksync_info(contract)
end
defp merge_zksync_info(smart_contract_info, contract) do
if Application.get_env(:explorer, :chain_type) == :zksync do
smart_contract_info
|> Map.merge(%{"ZkCompilerVersion" => contract.zk_compiler_version})
else
smart_contract_info
end
end
defp latest_decompiled_smart_contract(%NotLoaded{}), do: nil

@ -240,6 +240,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"is_blueprint" => if(smart_contract.is_blueprint, do: smart_contract.is_blueprint, else: false)
}
|> Map.merge(bytecode_info(address))
|> add_zksync_info(target_contract)
end
def prepare_smart_contract(address, implementations, proxy_type, conn) do
@ -291,6 +292,16 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
end
end
defp add_zksync_info(smart_contract_info, target_contract) do
if Application.get_env(:explorer, :chain_type) == :zksync do
Map.merge(smart_contract_info, %{
"zk_compiler_version" => target_contract.zk_compiler_version
})
else
smart_contract_info
end
end
defp prepare_external_libraries(libraries) when is_list(libraries) do
Enum.map(libraries, fn %Explorer.Chain.SmartContract.ExternalLibrary{name: name, address_hash: address_hash} ->
{:ok, hash} = Chain.string_to_address_hash(address_hash)
@ -338,26 +349,30 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
defp prepare_smart_contract_for_list(%SmartContract{} = smart_contract) do
token = smart_contract.address.token
%{
"address" =>
Helper.address_with_info(
nil,
%Address{smart_contract.address | smart_contract: smart_contract},
smart_contract.address.hash,
false
),
"compiler_version" => smart_contract.compiler_version,
"optimization_enabled" => smart_contract.optimization,
"tx_count" => smart_contract.address.transactions_count,
"language" => smart_contract_language(smart_contract),
"verified_at" => smart_contract.inserted_at,
"market_cap" => token && token.circulating_market_cap,
"has_constructor_args" => !is_nil(smart_contract.constructor_arguments),
"coin_balance" =>
if(smart_contract.address.fetched_coin_balance, do: smart_contract.address.fetched_coin_balance.value),
"license_type" => smart_contract.license_type,
"certified" => if(smart_contract.certified, do: smart_contract.certified, else: false)
}
smart_contract_info =
%{
"address" =>
Helper.address_with_info(
nil,
%Address{smart_contract.address | smart_contract: smart_contract},
smart_contract.address.hash,
false
),
"compiler_version" => smart_contract.compiler_version,
"optimization_enabled" => smart_contract.optimization,
"tx_count" => smart_contract.address.transactions_count,
"language" => smart_contract_language(smart_contract),
"verified_at" => smart_contract.inserted_at,
"market_cap" => token && token.circulating_market_cap,
"has_constructor_args" => !is_nil(smart_contract.constructor_arguments),
"coin_balance" =>
if(smart_contract.address.fetched_coin_balance, do: smart_contract.address.fetched_coin_balance.value),
"license_type" => smart_contract.license_type,
"certified" => if(smart_contract.certified, do: smart_contract.certified, else: false)
}
smart_contract_info
|> add_zksync_info(smart_contract)
end
defp smart_contract_language(smart_contract) do

@ -7,6 +7,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
setup :verify_on_exit!
if Application.compile_env(:explorer, :chain_type) == :zksync do
@optimization_runs "0"
else
@optimization_runs 200
end
def prepare_contracts do
insert(:contract_address)
{:ok, dt_1, _} = DateTime.from_iso8601("2022-09-20 10:00:00Z")
@ -108,7 +114,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [result(contract)]
result_props = result(contract) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == result(contract)[prop]
end
assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response)
end
@ -179,7 +189,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [result(contract)]
result_props = result(contract) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == result(contract)[prop]
end
assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response)
end
@ -204,7 +218,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [result(contract_2)]
result_props = result(contract_2) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == result(contract_2)[prop]
end
assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response)
end
@ -229,7 +247,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [result(contract_2), result(contract_3)]
result_props = result(contract_2) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == result(contract_2)[prop]
assert Enum.at(response["result"], 1)[prop] == result(contract_3)[prop]
end
assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response)
end
@ -254,7 +277,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [result(contract_1), result(contract_2)]
result_props = result(contract_1) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == result(contract_1)[prop]
assert Enum.at(response["result"], 1)[prop] == result(contract_2)[prop]
end
assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response)
end
@ -497,7 +525,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
contract =
insert(:smart_contract,
optimization: true,
optimization_runs: 200,
optimization_runs: @optimization_runs,
evm_version: "default",
contract_code_md5: "123"
)
@ -519,7 +547,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
# for `OptimizationUsed` is "1". If it was false, the expected value
# would be "0".
"OptimizationUsed" => "true",
"OptimizationRuns" => 200,
"OptimizationRuns" => @optimization_runs,
"EVMVersion" => "default",
"FileName" => "",
"IsProxy" => "false"
@ -533,7 +561,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
result_props = Enum.at(expected_result, 0) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == Enum.at(expected_result, 0)[prop]
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)
@ -751,7 +784,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
}
]
assert response["result"] == expected_result
result_props = Enum.at(expected_result, 0) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == Enum.at(expected_result, 0)[prop]
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)
@ -761,7 +799,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
contract =
insert(:smart_contract,
optimization: true,
optimization_runs: 200,
optimization_runs: @optimization_runs,
evm_version: "default",
constructor_arguments:
"00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546",
@ -782,7 +820,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => "true",
"OptimizationRuns" => 200,
"OptimizationRuns" => @optimization_runs,
"EVMVersion" => "default",
"ConstructorArguments" =>
"00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546",
@ -798,7 +836,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
result_props = Enum.at(expected_result, 0) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == Enum.at(expected_result, 0)[prop]
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)
@ -859,7 +902,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
}
],
optimization: true,
optimization_runs: 200,
optimization_runs: @optimization_runs,
evm_version: "default"
}
@ -884,7 +927,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => "true",
"OptimizationRuns" => 200,
"OptimizationRuns" => @optimization_runs,
"EVMVersion" => "default",
"ExternalLibraries" => [
%{"name" => "Test", "address_hash" => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95"},
@ -902,7 +945,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
result_props = Enum.at(expected_result, 0) |> Map.keys()
for prop <- result_props do
assert Enum.at(response["result"], 0)[prop] == Enum.at(expected_result, 0)[prop]
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)

@ -1,3 +1,85 @@
defmodule Explorer.Chain.SmartContract.Schema do
@moduledoc """
Models smart-contract.
"""
alias Explorer.Chain.SmartContract.ExternalLibrary
alias Explorer.Chain.{
Address,
DecompiledSmartContract,
Hash,
SmartContractAdditionalSource
}
case Application.compile_env(:explorer, :chain_type) do
:zksync ->
@chain_type_fields quote(
do: [
field(:optimization_runs, :string),
field(:zk_compiler_version, :string, null: true)
]
)
_ ->
@chain_type_fields quote(do: [field(:optimization_runs, :integer)])
end
defmacro generate do
quote do
typed_schema "smart_contracts" do
field(:name, :string, null: false)
field(:compiler_version, :string, null: false)
field(:optimization, :boolean, null: false)
field(:contract_source_code, :string, null: false)
field(:constructor_arguments, :string)
field(:evm_version, :string)
embeds_many(:external_libraries, ExternalLibrary, on_replace: :delete)
field(:abi, {:array, :map})
field(:verified_via_sourcify, :boolean)
field(:verified_via_eth_bytecode_db, :boolean)
field(:verified_via_verifier_alliance, :boolean)
field(:partially_verified, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
field(:is_changed_bytecode, :boolean, default: false)
field(:bytecode_checked_at, :utc_datetime_usec, default: DateTime.add(DateTime.utc_now(), -86400, :second))
field(:contract_code_md5, :string, null: false)
field(:compiler_settings, :map)
field(:autodetect_constructor_args, :boolean, virtual: true)
field(:is_yul, :boolean, virtual: true)
field(:metadata_from_verified_bytecode_twin, :boolean, virtual: true)
field(:license_type, Ecto.Enum, values: @license_enum, default: :none)
field(:certified, :boolean)
field(:is_blueprint, :boolean)
has_many(
:decompiled_smart_contracts,
DecompiledSmartContract,
foreign_key: :address_hash
)
belongs_to(
:address,
Address,
foreign_key: :address_hash,
references: :hash,
type: Hash.Address,
null: false
)
has_many(:smart_contract_additional_sources, SmartContractAdditionalSource,
references: :address_hash,
foreign_key: :address_hash
)
timestamps()
unquote_splicing(@chain_type_fields)
end
end
end
end
defmodule Explorer.Chain.SmartContract do
@moduledoc """
The representation of a verified Smart Contract.
@ -9,6 +91,7 @@ defmodule Explorer.Chain.SmartContract do
"""
require Logger
require Explorer.Chain.SmartContract.Schema
use Explorer.Schema
@ -19,7 +102,6 @@ defmodule Explorer.Chain.SmartContract do
Address,
ContractMethod,
Data,
DecompiledSmartContract,
Hash,
InternalTransaction,
SmartContract,
@ -29,7 +111,7 @@ defmodule Explorer.Chain.SmartContract do
alias Explorer.Chain.Address.Name, as: AddressName
alias Explorer.Chain.SmartContract.{ExternalLibrary, Proxy}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.SmartContract.Helper
alias Explorer.SmartContract.Solidity.Verifier
@ -39,6 +121,21 @@ defmodule Explorer.Chain.SmartContract do
@burn_address_hash_string "0x0000000000000000000000000000000000000000"
@dead_address_hash_string "0x000000000000000000000000000000000000dEaD"
@required_attrs ~w(compiler_version optimization address_hash contract_code_md5)a
@optional_common_attrs ~w(name contract_source_code evm_version optimization_runs constructor_arguments verified_via_sourcify verified_via_eth_bytecode_db verified_via_verifier_alliance partially_verified file_path is_vyper_contract is_changed_bytecode bytecode_checked_at autodetect_constructor_args license_type certified is_blueprint)a
@optional_changeset_attrs ~w(abi compiler_settings)a
@optional_invalid_contract_changeset_attrs ~w(autodetect_constructor_args)a
@chain_type_optional_attrs (case Application.compile_env(:explorer, :chain_type) do
:zksync ->
~w(zk_compiler_version)a
_ ->
~w()a
end)
@doc """
Returns burn address hash
"""
@ -260,6 +357,12 @@ defmodule Explorer.Chain.SmartContract do
* `name` - the human-readable name of the smart contract.
* `compiler_version` - the version of the Solidity compiler used to compile `contract_source_code` with `optimization`
into `address` `t:Explorer.Chain.Address.t/0` `contract_code`.
#{case Application.compile_env(:explorer, :chain_type) do
:zksync -> """
* `zk_compiler_version` - the version of ZkSolc or ZkVyper compilers.
"""
_ -> ""
end}
* `optimization` - whether optimizations were turned on when compiling `contract_source_code` into `address`
`t:Explorer.Chain.Address.t/0` `contract_code`.
* `contract_source_code` - the Solidity source code that was compiled by `compiler_version` with `optimization` to
@ -281,94 +384,24 @@ defmodule Explorer.Chain.SmartContract do
* `certified` - boolean flag, which can be set for set of smart-contracts via runtime env variable to prioritize those smart-contracts in the search.
* `is_blueprint` - boolean flag, determines if contract is ERC-5202 compatible blueprint contract or not.
"""
typed_schema "smart_contracts" do
field(:name, :string, null: false)
field(:compiler_version, :string, null: false)
field(:optimization, :boolean, null: false)
field(:contract_source_code, :string, null: false)
field(:constructor_arguments, :string)
field(:evm_version, :string)
field(:optimization_runs, :integer)
embeds_many(:external_libraries, ExternalLibrary, on_replace: :delete)
field(:abi, {:array, :map})
field(:verified_via_sourcify, :boolean)
field(:verified_via_eth_bytecode_db, :boolean)
field(:verified_via_verifier_alliance, :boolean)
field(:partially_verified, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
field(:is_changed_bytecode, :boolean, default: false)
field(:bytecode_checked_at, :utc_datetime_usec, default: DateTime.add(DateTime.utc_now(), -86400, :second))
field(:contract_code_md5, :string, null: false)
field(:compiler_settings, :map)
field(:autodetect_constructor_args, :boolean, virtual: true)
field(:is_yul, :boolean, virtual: true)
field(:metadata_from_verified_bytecode_twin, :boolean, virtual: true)
field(:license_type, Ecto.Enum, values: @license_enum, default: :none)
field(:certified, :boolean)
field(:is_blueprint, :boolean)
has_many(
:decompiled_smart_contracts,
DecompiledSmartContract,
foreign_key: :address_hash
)
belongs_to(
:address,
Address,
foreign_key: :address_hash,
references: :hash,
type: Hash.Address,
null: false
)
has_many(:smart_contract_additional_sources, SmartContractAdditionalSource,
references: :address_hash,
foreign_key: :address_hash
)
timestamps()
end
Explorer.Chain.SmartContract.Schema.generate()
def preload_decompiled_smart_contract(contract) do
Repo.preload(contract, :decompiled_smart_contracts)
end
def changeset(%__MODULE__{} = smart_contract, attrs) do
attrs_to_cast =
@required_attrs ++
@optional_common_attrs ++
@optional_changeset_attrs ++
@chain_type_optional_attrs
required_for_validation = [:name, :contract_source_code] ++ @required_attrs
smart_contract
|> cast(attrs, [
:name,
:compiler_version,
:optimization,
:contract_source_code,
:address_hash,
:abi,
:constructor_arguments,
:evm_version,
:optimization_runs,
:verified_via_sourcify,
:verified_via_eth_bytecode_db,
:verified_via_verifier_alliance,
:partially_verified,
:file_path,
:is_vyper_contract,
:is_changed_bytecode,
:bytecode_checked_at,
:contract_code_md5,
:compiler_settings,
:license_type,
:certified,
:is_blueprint
])
|> validate_required([
:name,
:compiler_version,
:optimization,
:contract_source_code,
:address_hash,
:contract_code_md5
])
|> cast(attrs, attrs_to_cast)
|> validate_required(required_for_validation)
|> unique_constraint(:address_hash)
|> prepare_changes(&upsert_contract_methods/1)
end
@ -380,34 +413,18 @@ defmodule Explorer.Chain.SmartContract do
error_message,
verification_with_files? \\ false
) do
attrs_to_cast =
@required_attrs ++
@optional_common_attrs ++
@optional_invalid_contract_changeset_attrs ++
@chain_type_optional_attrs
validated =
smart_contract
|> cast(attrs, [
:name,
:compiler_version,
:optimization,
:contract_source_code,
:address_hash,
:evm_version,
:optimization_runs,
:constructor_arguments,
:verified_via_sourcify,
:verified_via_eth_bytecode_db,
:verified_via_verifier_alliance,
:partially_verified,
:file_path,
:is_vyper_contract,
:is_changed_bytecode,
:bytecode_checked_at,
:contract_code_md5,
:autodetect_constructor_args,
:license_type,
:certified,
:is_blueprint
])
|> cast(attrs, attrs_to_cast)
|> (&if(verification_with_files?,
do: &1,
else: validate_required(&1, [:compiler_version, :optimization, :address_hash, :contract_code_md5])
else: validate_required(&1, @required_attrs)
)).()
field_to_put_message = if verification_with_files?, do: :files, else: select_error_field(error)
@ -467,15 +484,24 @@ defmodule Explorer.Chain.SmartContract do
end
def merge_twin_contract_with_changeset(nil, %Changeset{} = changeset) do
optimization_runs =
if Application.get_env(:explorer, :chain_type) == :zksync,
do: "0",
else: "200"
changeset
|> Changeset.put_change(:name, "")
|> Changeset.put_change(:optimization_runs, "200")
|> Changeset.put_change(:optimization_runs, optimization_runs)
|> Changeset.put_change(:optimization, true)
|> Changeset.put_change(:evm_version, "default")
|> Changeset.put_change(:compiler_version, "latest")
|> Changeset.put_change(:contract_source_code, "")
|> Changeset.put_change(:autodetect_constructor_args, true)
|> Changeset.put_change(:is_yul, false)
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: Changeset.put_change(&1, :zk_compiler_version, "latest"),
else: &1
)).()
end
def merge_twin_vyper_contract_with_changeset(
@ -500,6 +526,10 @@ defmodule Explorer.Chain.SmartContract do
|> Changeset.put_change(:name, "Vyper_contract")
|> Changeset.put_change(:compiler_version, "latest")
|> Changeset.put_change(:contract_source_code, "")
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: Changeset.put_change(&1, :zk_compiler_version, "latest"),
else: &1
)).()
end
def license_types_enum, do: @license_enum

@ -296,6 +296,7 @@ defmodule Explorer.ChainSpec.GenesisData do
address_hash: contract["address"],
name: contract["name"],
file_path: nil,
# todo: process zksync zk_compiler
compiler_version: contract["compiler"],
evm_version: nil,
optimization_runs: nil,

@ -12,11 +12,12 @@ defmodule Explorer.SmartContract.CompilerVersion do
@doc """
Fetches a list of compilers from the Ethereum Solidity API.
"""
@spec fetch_versions(:solc | :vyper) :: {atom, [map]}
@spec fetch_versions(:solc | :vyper | :zk) :: {atom, [map]}
def fetch_versions(compiler) do
case compiler do
:solc -> fetch_solc_versions()
:vyper -> fetch_vyper_versions()
:zk -> fetch_zk_versions()
end
end
@ -34,26 +35,58 @@ defmodule Explorer.SmartContract.CompilerVersion do
fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :solc)
end
defp fetch_zk_versions do
fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :zk)
end
defp fetch_vyper_versions do
fetch_compiler_versions(&RustVerifierInterface.vyper_get_versions_list/0, :vyper)
end
defp fetch_compiler_versions(compiler_list_fn, compiler_type) do
if RustVerifierInterface.enabled?() do
compiler_list_fn.()
fetch_compiler_versions_sc_verified_enabled(compiler_list_fn, compiler_type)
else
headers = [{"Content-Type", "application/json"}]
if compiler_type == :zk do
{:ok, []}
else
headers = [{"Content-Type", "application/json"}]
case HTTPoison.get(source_url(compiler_type), headers) do
{:ok, %{status_code: 200, body: body}} ->
{:ok, format_data(body, compiler_type)}
# credo:disable-for-next-line
case HTTPoison.get(source_url(compiler_type), headers) do
{:ok, %{status_code: 200, body: body}} ->
{:ok, format_data(body, compiler_type)}
{:ok, %{status_code: _status_code, body: body}} ->
{:error, Helper.decode_json(body)["error"]}
{:error, %{reason: reason}} ->
{:error, reason}
end
end
end
end
{:ok, %{status_code: _status_code, body: body}} ->
{:error, Helper.decode_json(body)["error"]}
defp fetch_compiler_versions_sc_verified_enabled(compiler_list_fn, compiler_type) do
if Application.get_env(:explorer, :chain_type) == :zksync do
# todo: refactor opportunity, currently, Blockscout 2 identical requests to microservice in order to get
# Solc and Zk compiler versions
case compiler_list_fn.() do
{:ok, {solc_compilers, zk_compilers}} ->
choose_compiler(compiler_type, %{:solc_compilers => solc_compilers, :zk_compilers => zk_compilers})
{:error, %{reason: reason}} ->
{:error, reason}
_ ->
{:error, "Verifier microservice is unavailable"}
end
else
compiler_list_fn.()
end
end
defp choose_compiler(compiler_type, compilers) do
case compiler_type do
:solc -> {:ok, compilers.solc_compilers}
:zk -> {:ok, compilers.zk_compilers}
end
end

@ -115,9 +115,14 @@ defmodule Explorer.SmartContract.Helper do
def prepare_bytecode_for_microservice(body, creation_input, deployed_bytecode)
def prepare_bytecode_for_microservice(body, empty, deployed_bytecode) when is_nil(empty) do
body
|> Map.put("bytecodeType", "DEPLOYED_BYTECODE")
|> Map.put("bytecode", deployed_bytecode)
if Application.get_env(:explorer, :chain_type) == :zksync do
body
|> Map.put("code", deployed_bytecode)
else
body
|> Map.put("bytecodeType", "DEPLOYED_BYTECODE")
|> Map.put("bytecode", deployed_bytecode)
end
end
def prepare_bytecode_for_microservice(body, creation_bytecode, _deployed_bytecode) do
@ -173,15 +178,19 @@ defmodule Explorer.SmartContract.Helper do
"chainId" => Application.get_env(:block_scout_web, :chain_id)
}
case SmartContract.creation_tx_with_bytecode(address_hash) do
%{init: init, tx: tx} ->
{init, deployed_bytecode, tx |> tx_to_metadata(init) |> Map.merge(metadata)}
if Application.get_env(:explorer, :chain_type) == :zksync do
{nil, deployed_bytecode, metadata}
else
case SmartContract.creation_tx_with_bytecode(address_hash) do
%{init: init, tx: tx} ->
{init, deployed_bytecode, tx |> tx_to_metadata(init) |> Map.merge(metadata)}
%{init: init, internal_tx: internal_tx} ->
{init, deployed_bytecode, internal_tx |> internal_tx_to_metadata(init) |> Map.merge(metadata)}
%{init: init, internal_tx: internal_tx} ->
{init, deployed_bytecode, internal_tx |> internal_tx_to_metadata(init) |> Map.merge(metadata)}
_ ->
{nil, deployed_bytecode, metadata}
_ ->
{nil, deployed_bytecode, metadata}
end
end
end

@ -39,6 +39,18 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
http_post_request(solidity_standard_json_verification_url(), append_metadata(body, metadata), true)
end
def zksync_verify_standard_json_input(
%{
"code" => _,
"solcCompiler" => _,
"zkCompiler" => _,
"input" => _
} = body,
metadata
) do
http_post_request(solidity_standard_json_verification_url(), append_metadata(body, metadata), true)
end
def vyper_verify_multipart(
%{
"bytecode" => _,
@ -146,32 +158,64 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do
{:ok, source}
end
# zksync
def process_verifier_response(%{"verificationSuccess" => success}, _) do
{:ok, success}
end
# zksync
def process_verifier_response(%{"verificationFailure" => %{"message" => error}}, _) do
{:error, error}
end
# zksync
def process_verifier_response(%{"compilationFailure" => %{"message" => error}}, _) do
{:error, error}
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}
# zksync
def process_verifier_response(%{"solcCompilers" => solc_compilers, "zkCompilers" => zk_compilers}, _),
do: {:ok, {solc_compilers, zk_compilers}}
def process_verifier_response(other, res) do
{:error, other}
end
# Uses url encoded ("%3A") version of ':', as ':' symbol breaks `Bypass` library during tests.
# https://github.com/PSPDFKit-labs/bypass/issues/122
def solidity_multiple_files_verification_url,
do: "#{base_api_url()}" <> "/verifier/solidity/sources%3Averify-multi-part"
do: base_api_url() <> "/verifier/solidity/sources%3Averify-multi-part"
def vyper_multiple_files_verification_url,
do: "#{base_api_url()}" <> "/verifier/vyper/sources%3Averify-multi-part"
do: base_api_url() <> "/verifier/vyper/sources%3Averify-multi-part"
def vyper_standard_json_verification_url,
do: "#{base_api_url()}" <> "/verifier/vyper/sources%3Averify-standard-json"
do: base_api_url() <> "/verifier/vyper/sources%3Averify-standard-json"
def solidity_standard_json_verification_url,
do: "#{base_api_url()}" <> "/verifier/solidity/sources%3Averify-standard-json"
def solidity_standard_json_verification_url do
base_api_url() <> verifier_path() <> "/solidity/sources%3Averify-standard-json"
end
def versions_list_url do
base_api_url() <> verifier_path() <> "/solidity/versions"
end
def versions_list_url, do: "#{base_api_url()}" <> "/verifier/solidity/versions"
defp verifier_path do
if Application.get_env(:explorer, :chain_type) == :zksync do
"/zksync-verifier"
else
"/verifier"
end
end
def vyper_versions_list_url, do: "#{base_api_url()}" <> "/verifier/vyper/versions"
def vyper_versions_list_url, do: base_api_url() <> "/verifier/vyper/versions"
def base_api_url, do: "#{base_url()}" <> "/api/v2"

@ -86,6 +86,26 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
} = result_params} ->
process_rust_verifier_response(result_params, address_hash, params, true, true)
# zksync
{:ok,
%{
"compilationArtifacts" => compilation_artifacts_string,
"evmCompiler" => _,
"zkCompiler" => _,
"contractName" => _,
"fileName" => _,
"sources" => _,
"compilerSettings" => _,
"runtimeMatch" => _
} = result_params} ->
compilation_artifacts = Jason.decode!(compilation_artifacts_string)
transformed_result_params =
result_params
|> Map.put("abi", Map.get(compilation_artifacts, "abi"))
process_rust_verifier_response(transformed_result_params, address_hash, params, true, true)
{:ok, %{abi: abi, constructor_arguments: constructor_arguments}, additional_params} ->
params_with_constructor_arguments =
params
@ -134,6 +154,73 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
end
def process_rust_verifier_response(
source,
address_hash,
initial_params,
is_standard_json?,
save_file_path?,
automatically_verified? \\ false
)
# zksync
def process_rust_verifier_response(
%{
"abi" => abi,
"evmCompiler" => %{"version" => compiler_version},
"contractName" => contract_name,
"fileName" => file_name,
"sources" => sources,
"compilerSettings" => compiler_settings_string,
"runtimeMatch" => %{"type" => match_type},
"zkCompiler" => %{"version" => zk_compiler_version}
},
address_hash,
initial_params,
is_standard_json?,
save_file_path?,
_automatically_verified?
) 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)
optimization = extract_optimization(compiler_settings)
optimization_runs = zksync_parse_optimization_runs(compiler_settings, optimization)
constructor_arguments =
if initial_params["constructor_arguments"] !== "0x", do: initial_params["constructor_arguments"], else: nil
prepared_params =
%{}
|> Map.put("optimization", optimization)
|> Map.put("optimization_runs", optimization_runs)
|> Map.put("evm_version", compiler_settings["evmVersion"] || "default")
|> Map.put("compiler_version", compiler_version)
|> Map.put("zk_compiler_version", zk_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("compiler_settings", if(is_standard_json?, do: compiler_settings))
|> Map.put("partially_verified", match_type == "PARTIAL")
|> Map.put("verified_via_sourcify", false)
|> Map.put("verified_via_eth_bytecode_db", false)
|> Map.put("verified_via_verifier_alliance", false)
|> Map.put("license_type", initial_params["license_type"])
|> Map.put("is_blueprint", false)
publish_smart_contract(address_hash, prepared_params, abi)
end
def process_rust_verifier_response(
%{
"abi" => abi_string,
@ -149,7 +236,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
initial_params,
is_standard_json?,
save_file_path?,
automatically_verified? \\ false
automatically_verified?
) do
secondary_sources =
for {file, source} <- sources,
@ -162,10 +249,12 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
optimization = extract_optimization(compiler_settings)
optimization_runs = parse_optimization_runs(compiler_settings, optimization)
prepared_params =
%{}
|> Map.put("optimization", optimization)
|> Map.put("optimization_runs", if(optimization, do: compiler_settings["optimizer"]["runs"]))
|> Map.put("optimization_runs", optimization_runs)
|> Map.put("evm_version", compiler_settings["evmVersion"] || "default")
|> Map.put("compiler_version", compiler_version)
|> Map.put("constructor_arguments", constructor_arguments)
@ -185,6 +274,18 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string || "null"))
end
defp parse_optimization_runs(compiler_settings, optimization) do
if(optimization, do: compiler_settings["optimizer"]["runs"])
end
defp zksync_parse_optimization_runs(compiler_settings, optimization) do
optimizer = Map.get(compiler_settings, "optimizer")
if optimization do
if optimizer && Map.has_key?(optimizer, "mode"), do: Map.get(optimizer, "mode"), else: "3"
end
end
def extract_optimization(compiler_settings),
do: (compiler_settings["optimizer"] && compiler_settings["optimizer"]["enabled"]) || false
@ -262,6 +363,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
Map.put(attributes(address_hash, params, abi), :file_path, file_path)
end
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
defp attributes(address_hash, params, abi \\ %{}) do
constructor_arguments = params["constructor_arguments"]
compiler_settings = params["compiler_settings"]
@ -279,30 +381,37 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
compiler_version = CompilerVersion.get_strict_compiler_version(:solc, params["compiler_version"])
%{
address_hash: address_hash,
name: params["name"],
file_path: params["file_path"],
compiler_version: compiler_version,
evm_version: params["evm_version"],
optimization_runs: params["optimization_runs"],
optimization: params["optimization"],
contract_source_code: params["contract_source_code"],
constructor_arguments: clean_constructor_arguments,
external_libraries: prepared_external_libraries,
secondary_sources: params["secondary_sources"],
abi: abi,
verified_via_sourcify: params["verified_via_sourcify"] || false,
verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false,
verified_via_verifier_alliance: params["verified_via_verifier_alliance"] || false,
partially_verified: params["partially_verified"] || false,
is_vyper_contract: false,
autodetect_constructor_args: params["autodetect_constructor_args"],
is_yul: params["is_yul"] || false,
compiler_settings: clean_compiler_settings,
license_type: prepare_license_type(params["license_type"]) || :none,
is_blueprint: params["is_blueprint"] || false
}
base_attributes =
%{
address_hash: address_hash,
name: params["name"],
file_path: params["file_path"],
compiler_version: compiler_version,
evm_version: params["evm_version"],
optimization_runs: params["optimization_runs"],
optimization: params["optimization"],
contract_source_code: params["contract_source_code"],
constructor_arguments: clean_constructor_arguments,
external_libraries: prepared_external_libraries,
secondary_sources: params["secondary_sources"],
abi: abi,
verified_via_sourcify: params["verified_via_sourcify"] || false,
verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false,
verified_via_verifier_alliance: params["verified_via_verifier_alliance"] || false,
partially_verified: params["partially_verified"] || false,
is_vyper_contract: false,
autodetect_constructor_args: params["autodetect_constructor_args"],
is_yul: params["is_yul"] || false,
compiler_settings: clean_compiler_settings,
license_type: prepare_license_type(params["license_type"]) || :none,
is_blueprint: params["is_blueprint"] || false
}
base_attributes
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: Map.put(&1, :zk_compiler_version, params["zk_compiler_version"]),
else: &1
)).()
end
defp clear_constructor_arguments(constructor_arguments) do

@ -25,6 +25,8 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
@bytecode_hash_options ["default", "none", "bzzr1"]
@optimization_runs 200
def evaluate_authenticity(_, %{"contract_source_code" => ""}),
do: {:error, :contract_source_code}
@ -127,10 +129,23 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do
{creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
%{"compilerVersion" => params["compiler_version"]}
compiler_version_map =
if Application.get_env(:explorer, :chain_type) == :zksync do
%{
"solcCompiler" => params["compiler_version"],
"zkCompiler" => params["zk_compiler_version"]
}
else
%{"compilerVersion" => params["compiler_version"]}
end
compiler_version_map
|> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode)
|> Map.put("input", json_input)
|> RustVerifierInterface.verify_standard_json_input(verifier_metadata)
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: RustVerifierInterface.zksync_verify_standard_json_input(&1, verifier_metadata),
else: RustVerifierInterface.verify_standard_json_input(&1, verifier_metadata)
)).()
end
def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do
@ -215,6 +230,11 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
defp extract_settings_from_json(json_input) when is_map(json_input) do
%{"enabled" => optimization, "runs" => optimization_runs} = json_input["settings"]["optimizer"]
optimization_runs =
if Application.get_env(:explorer, :chain_type) == :zksync,
do: to_string(optimization_runs),
else: optimization_runs
%{"optimization" => optimization}
|> (&if(parse_boolean(optimization), do: Map.put(&1, "optimization_runs", optimization_runs), else: &1)).()
end
@ -227,7 +247,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
external_libraries = Map.get(params, "external_libraries", %{})
constructor_arguments = Map.get(params, "constructor_arguments", "")
evm_version = Map.get(params, "evm_version")
optimization_runs = Map.get(params, "optimization_runs", 200)
optimization_runs = Map.get(params, "optimization_runs", @optimization_runs)
autodetect_constructor_arguments = params |> Map.get("autodetect_constructor_args", "true") |> parse_boolean()
if is_compiler_version_at_least_0_6_0?(compiler_version) do

@ -367,13 +367,21 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
contract_name = settings |> Map.get("compilationTarget") |> Map.get("#{compilation_target_file_path}")
optimizer = Map.get(settings, "optimizer")
runs =
optimizer
|> Map.get("runs")
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: to_string(&1),
else: &1
)).()
params =
%{}
|> Map.put("name", contract_name)
|> Map.put("compiler_version", compiler_version)
|> Map.put("evm_version", Map.get(settings, "evmVersion"))
|> Map.put("optimization", Map.get(optimizer, "enabled"))
|> Map.put("optimization_runs", Map.get(optimizer, "runs"))
|> Map.put("optimization_runs", runs)
|> Map.put("external_libraries", Map.get(settings, "libraries"))
|> Map.put("verified_via_sourcify", true)
|> Map.put("compiler_settings", settings)

@ -0,0 +1,10 @@
defmodule Explorer.Repo.ZkSync.Migrations.AddZkCompilerVersionToSmartContracts do
use Ecto.Migration
def change do
alter table(:smart_contracts) do
add(:zk_compiler_version, :string, null: true)
modify(:optimization_runs, :string, null: true)
end
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,80 +1,82 @@
defmodule Explorer.SmartContract.Vyper.PublisherTest do
use ExUnit.Case, async: true
if Application.compile_env(:explorer, :chain_type) !== :zksync do
defmodule Explorer.SmartContract.Vyper.PublisherTest do
use ExUnit.Case, async: true
use Explorer.DataCase
use Explorer.DataCase
doctest Explorer.SmartContract.Vyper.Publisher
doctest Explorer.SmartContract.Vyper.Publisher
@moduletag timeout: :infinity
@moduletag timeout: :infinity
alias Explorer.Chain.{SmartContract}
alias Explorer.Factory
alias Explorer.SmartContract.Vyper.Publisher
alias Explorer.Chain.{SmartContract}
alias Explorer.Factory
alias Explorer.SmartContract.Vyper.Publisher
setup do
configuration = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, enabled: false)
setup do
configuration = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, enabled: false)
on_exit(fn ->
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, configuration)
end)
end
on_exit(fn ->
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, configuration)
end)
end
describe "publish/2" do
test "with valid data creates a smart_contract" do
contract_code_info = Factory.contract_code_info_vyper()
describe "publish/2" do
test "with valid data creates a smart_contract" do
contract_code_info = Factory.contract_code_info_vyper()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.address_hash == contract_address.hash
assert smart_contract.name == valid_attrs["name"]
assert smart_contract.compiler_version == valid_attrs["compiler_version"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert is_nil(smart_contract.constructor_arguments)
assert smart_contract.abi == contract_code_info.abi
end
assert smart_contract.address_hash == contract_address.hash
assert smart_contract.name == valid_attrs["name"]
assert smart_contract.compiler_version == valid_attrs["compiler_version"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert is_nil(smart_contract.constructor_arguments)
assert smart_contract.abi == contract_code_info.abi
end
test "allows to re-verify vyper contracts" do
contract_code_info = Factory.contract_code_info_vyper()
test "allows to re-verify vyper contracts" do
contract_code_info = Factory.contract_code_info_vyper()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{}} = response
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{}} = response
updated_name = "AnotherContractName"
updated_name = "AnotherContractName"
valid_attrs =
valid_attrs
|> Map.put("name", updated_name)
valid_attrs =
valid_attrs
|> Map.put("name", updated_name)
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.name == valid_attrs["name"]
assert smart_contract.name == valid_attrs["name"]
end
end
end
end

@ -63,6 +63,12 @@ defmodule Explorer.Factory do
alias Ueberauth.Auth.Info
alias Ueberauth.Auth
if Application.compile_env(:explorer, :chain_type) == :zksync do
@optimization_runs "1"
else
@optimization_runs 1
end
def account_identity_factory do
%Identity{
uid: sequence("github|"),
@ -446,7 +452,7 @@ defmodule Explorer.Factory do
],
version: "v0.8.4+commit.c7e474f2",
optimized: true,
optimization_runs: 1,
optimization_runs: @optimization_runs,
constructor_args:
"00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000756b5b30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000006ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd8f0000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000371776500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003657771000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000097177657177657177650000000000000000000000000000000000000000000000"
}

Loading…
Cancel
Save