feat: Support zksync foundry verification (#11037)

* feat: Support zksync foundry verification

* Fix constructor args

* Fix credo

* Process review comments
pull/11013/merge
nikitosing 3 weeks ago committed by GitHub
parent 92a814d22b
commit 75519fee63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  2. 14
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
  3. 21
      apps/explorer/lib/explorer/chain.ex
  4. 50
      apps/explorer/lib/explorer/chain/smart_contract.ex
  5. 16
      apps/explorer/lib/explorer/smart_contract/helper.ex
  6. 9
      apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
  7. 7
      apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
  8. 1
      cspell.json

@ -631,6 +631,10 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
|> required_param(params, "compilerversion", "compiler_version")
|> optional_param(params, "constructorArguments", "constructor_arguments")
|> optional_param(params, "licenseType", "license_type")
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,
do: optional_param(&1, params, "zksolcVersion", "zk_compiler_version"),
else: &1
)).()
end
defp fetch_verifysourcecode_solidity_single_file_params(params) do

@ -122,21 +122,13 @@ 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", constructor_arguments)
|> Map.put("constructor_arguments", Map.get(params, "constructor_args", ""))
|> 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,
@ -320,10 +312,6 @@ 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),

@ -3200,6 +3200,27 @@ defmodule Explorer.Chain do
end
end
@doc """
Fetches contract creation input data from the transaction (not internal transaction).
"""
@spec contract_creation_input_data_from_transaction(String.t()) :: nil | binary()
def contract_creation_input_data_from_transaction(address_hash, options \\ []) do
transaction =
Transaction
|> where([transaction], transaction.created_contract_address_hash == ^address_hash)
|> select_repo(options).one()
if transaction && transaction.input do
case Data.dump(transaction.input) do
{:ok, bytes} ->
bytes
_ ->
nil
end
end
end
@doc """
Fetches contract creation input data.
"""

@ -95,6 +95,7 @@ defmodule Explorer.Chain.SmartContract do
use Explorer.Schema
alias ABI.FunctionSelector
alias Ecto.{Changeset, Multi}
alias Explorer.{Chain, Repo, SortingHelper}
@ -137,6 +138,31 @@ defmodule Explorer.Chain.SmartContract do
~w()a
end)
@create_zksync_abi [
%{
"inputs" => [
%{"internalType" => "bytes32", "name" => "_salt", "type" => "bytes32"},
%{"internalType" => "bytes32", "name" => "_bytecodeHash", "type" => "bytes32"},
%{"internalType" => "bytes", "name" => "_input", "type" => "bytes"}
],
"name" => "create2",
"outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}],
"stateMutability" => "payable",
"type" => "function"
},
%{
"inputs" => [
%{"internalType" => "bytes32", "name" => "_salt", "type" => "bytes32"},
%{"internalType" => "bytes32", "name" => "_bytecodeHash", "type" => "bytes32"},
%{"internalType" => "bytes", "name" => "_input", "type" => "bytes"}
],
"name" => "create",
"outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}],
"stateMutability" => "payable",
"type" => "function"
}
]
@doc """
Returns burn address hash
"""
@ -1310,4 +1336,28 @@ defmodule Explorer.Chain.SmartContract do
end
defp filter_contracts(basic_query, _), do: basic_query
@doc """
Retrieves the constructor arguments for a zkSync smart contract.
Using @create_zksync_abi function decodes transaction input of contract creation
## Parameters
- `binary()`: The binary data representing the smart contract.
## Returns
- `nil`: If the constructor arguments cannot be retrieved.
- `binary()`: The constructor arguments in binary format.
"""
@spec zksync_get_constructor_arguments(binary()) :: nil | binary()
def zksync_get_constructor_arguments(address_hash_string) do
creation_input = Chain.contract_creation_input_data_from_transaction(address_hash_string)
case @create_zksync_abi |> ABI.parse_specification() |> ABI.find_and_decode(creation_input) do
{%FunctionSelector{}, [_, _, constructor_args]} ->
Base.encode16(constructor_args, case: :lower)
_ ->
nil
end
end
end

@ -112,9 +112,23 @@ defmodule Explorer.SmartContract.Helper do
end
end
@doc """
Prepares the bytecode for a microservice by processing the given body, creation input, and deployed bytecode.
## Parameters
- body: The body of the request or data to be processed.
- creation_input: The input data used during the creation of the smart contract.
- deployed_bytecode: The bytecode of the deployed smart contract.
## Returns
The processed bytecode ready for the microservice.
"""
@spec prepare_bytecode_for_microservice(map(), binary() | nil, binary() | nil) :: map()
def prepare_bytecode_for_microservice(body, creation_input, deployed_bytecode)
def prepare_bytecode_for_microservice(body, empty, deployed_bytecode) when is_nil(empty) do
def prepare_bytecode_for_microservice(body, creation_input, deployed_bytecode) when is_nil(creation_input) do
if Application.get_env(:explorer, :chain_type) == :zksync do
body
|> Map.put("code", deployed_bytecode)

@ -72,6 +72,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
def publish_with_standard_json_input(%{"address_hash" => address_hash} = params, json_input) do
Logger.info(@sc_verification_via_standard_json_input_started)
params = maybe_add_zksync_specific_data(params)
case Verifier.evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do
{:ok,
@ -452,4 +453,12 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
Map.put(params, "external_libraries", clean_external_libraries)
end
defp maybe_add_zksync_specific_data(params) do
if Application.get_env(:explorer, :chain_type) == :zksync do
Map.put(params, "constructor_arguments", SmartContract.zksync_get_constructor_arguments(params["address_hash"]))
else
params
end
end
end

@ -129,17 +129,18 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do
{creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash)
compiler_version_map =
verification_params =
if Application.get_env(:explorer, :chain_type) == :zksync do
%{
"solcCompiler" => params["compiler_version"],
"zkCompiler" => params["zk_compiler_version"]
"zkCompiler" => params["zk_compiler_version"],
"constructorArguments" => params["constructor_arguments"]
}
else
%{"compilerVersion" => params["compiler_version"]}
end
compiler_version_map
verification_params
|> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode)
|> Map.put("input", json_input)
|> (&if(Application.get_env(:explorer, :chain_type) == :zksync,

@ -641,6 +641,7 @@
"zkatana",
"zkbob",
"zkevm",
"zksolc",
"zksync"
],
"enableFiletypes": [

Loading…
Cancel
Save