feat: Add Stylus verificaiton support (#11183)
* feat: Add Stylus verificaiton support * Fix tests * Apply suggestions from code review Co-authored-by: Alexander Kolotov <alexandr.kolotov@gmail.com> * Refactoring + handle snake case * Add env to docker-compose/envs/common-blockscout.env * Apply suggestions from code review Co-authored-by: Alexander Kolotov <alexandr.kolotov@gmail.com> * Fix cspell * Drop MICROSERVICE_STYLUS_VERIFIER_ENABLED * Fix tests * Fix tests * Apply suggestions from code review Co-authored-by: Alexander Kolotov <alexandr.kolotov@gmail.com> * Process review comments * Fix dialyzer --------- Co-authored-by: Alexander Kolotov <alexandr.kolotov@gmail.com>master
parent
c4e6f9c384
commit
d5709dfda9
@ -1,6 +1,8 @@ |
|||||||
lib/ethereum_jsonrpc/rolling_window.ex:171 |
lib/ethereum_jsonrpc/rolling_window.ex:171 |
||||||
lib/explorer/smart_contract/solidity/publisher_worker.ex:1 |
lib/explorer/smart_contract/solidity/publisher_worker.ex:1 |
||||||
lib/explorer/smart_contract/vyper/publisher_worker.ex:1 |
lib/explorer/smart_contract/vyper/publisher_worker.ex:1 |
||||||
|
lib/explorer/smart_contract/stylus/publisher_worker.ex:1 |
||||||
lib/explorer/smart_contract/solidity/publisher_worker.ex:8 |
lib/explorer/smart_contract/solidity/publisher_worker.ex:8 |
||||||
lib/explorer/smart_contract/vyper/publisher_worker.ex:8 |
lib/explorer/smart_contract/vyper/publisher_worker.ex:8 |
||||||
|
lib/explorer/smart_contract/stylus/publisher_worker.ex:8 |
||||||
lib/phoenix/router.ex:402 |
lib/phoenix/router.ex:402 |
||||||
|
@ -0,0 +1,235 @@ |
|||||||
|
defmodule Explorer.SmartContract.Stylus.Publisher do |
||||||
|
@moduledoc """ |
||||||
|
Module responsible for verifying and publishing Stylus smart contracts. |
||||||
|
|
||||||
|
The verification process includes: |
||||||
|
1. Initiating verification through a microservice that compares GitHub repository |
||||||
|
source code against deployed bytecode |
||||||
|
2. Processing the verification response, including ABI and source files |
||||||
|
3. Creating or updating the smart contract record in the database |
||||||
|
4. Handling verification failures by creating invalid changesets with error messages |
||||||
|
""" |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
alias Explorer.Chain.SmartContract |
||||||
|
alias Explorer.SmartContract.Helper |
||||||
|
alias Explorer.SmartContract.Stylus.Verifier |
||||||
|
|
||||||
|
@default_file_name "src/lib.rs" |
||||||
|
|
||||||
|
@sc_verification_via_github_repository_started "Smart-contract verification via Github repository started" |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Verifies and publishes a Stylus smart contract using GitHub repository source code. |
||||||
|
|
||||||
|
Initiates verification of a contract through the verification microservice. On |
||||||
|
successful verification, processes and stores the contract details in the |
||||||
|
database. On failure, creates an invalid changeset with appropriate error |
||||||
|
messages. |
||||||
|
|
||||||
|
## Parameters |
||||||
|
- `address_hash`: The contract's address hash as binary or `t:Explorer.Chain.Hash.t/0` |
||||||
|
- `params`: Map containing verification parameters: |
||||||
|
- `"cargo_stylus_version"`: Version of cargo-stylus used for deployment |
||||||
|
- `"repository_url"`: GitHub repository URL containing contract code |
||||||
|
- `"commit"`: Git commit hash used for deployment |
||||||
|
- `"path_prefix"`: Optional path prefix if contract is not in repository root |
||||||
|
|
||||||
|
## Returns |
||||||
|
- `{:ok, smart_contract}` if verification and database storage succeed |
||||||
|
- `{:error, changeset}` if verification fails or there are validation errors |
||||||
|
""" |
||||||
|
@spec publish(binary() | Explorer.Chain.Hash.t(), %{String.t() => any()}) :: |
||||||
|
{:error, Ecto.Changeset.t()} | {:ok, Explorer.Chain.SmartContract.t()} |
||||||
|
def publish(address_hash, params) do |
||||||
|
Logger.info(@sc_verification_via_github_repository_started) |
||||||
|
|
||||||
|
case Verifier.evaluate_authenticity(address_hash, params) do |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%{ |
||||||
|
"abi" => _, |
||||||
|
"cargo_stylus_version" => _, |
||||||
|
"contract_name" => _, |
||||||
|
"files" => _, |
||||||
|
"package_name" => _, |
||||||
|
"github_repository_metadata" => _ |
||||||
|
} = result_params |
||||||
|
} -> |
||||||
|
process_verifier_response(result_params, address_hash) |
||||||
|
|
||||||
|
{:error, error} -> |
||||||
|
{:error, unverified_smart_contract(address_hash, params, error, nil)} |
||||||
|
|
||||||
|
_ -> |
||||||
|
{:error, unverified_smart_contract(address_hash, params, "Unexpected error", nil)} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
# Processes successful Stylus contract verification response and stores contract data. |
||||||
|
# |
||||||
|
# Takes the verification response from `evaluate_authenticity/2` containing verified contract |
||||||
|
# details and prepares them for storage in the database. The main source file is extracted |
||||||
|
# from `files` map using the default filename, while other files are stored as secondary |
||||||
|
# sources. |
||||||
|
# |
||||||
|
# ## Parameters |
||||||
|
# - `response`: Verification response map containing: |
||||||
|
# - `abi`: Contract ABI as JSON string |
||||||
|
# - `cargo_stylus_version`: Version of cargo-stylus used |
||||||
|
# - `contract_name`: Name of the contract |
||||||
|
# - `files`: Map of file paths to source code contents |
||||||
|
# - `package_name`: Package name of the contract |
||||||
|
# - `github_repository_metadata`: Repository metadata |
||||||
|
# - `address_hash`: The contract's address hash as binary or `t:Explorer.Chain.Hash.t/0` |
||||||
|
# |
||||||
|
# ## Returns |
||||||
|
# - `{:ok, smart_contract}` if database storage succeeds |
||||||
|
# - `{:error, changeset}` if there are validation errors |
||||||
|
# - `{:error, message}` if the database operation fails |
||||||
|
@spec process_verifier_response(%{String.t() => any()}, binary() | Explorer.Chain.Hash.t()) :: |
||||||
|
{:ok, Explorer.Chain.SmartContract.t()} | {:error, Ecto.Changeset.t() | String.t()} |
||||||
|
defp process_verifier_response( |
||||||
|
%{ |
||||||
|
"abi" => abi_string, |
||||||
|
"cargo_stylus_version" => cargo_stylus_version, |
||||||
|
"contract_name" => contract_name, |
||||||
|
"files" => files, |
||||||
|
"package_name" => package_name, |
||||||
|
"github_repository_metadata" => github_repository_metadata |
||||||
|
}, |
||||||
|
address_hash |
||||||
|
) do |
||||||
|
secondary_sources = |
||||||
|
for {file, code} <- files, |
||||||
|
file != @default_file_name, |
||||||
|
do: %{"file_name" => file, "contract_source_code" => code, "address_hash" => address_hash} |
||||||
|
|
||||||
|
contract_source_code = files[@default_file_name] |
||||||
|
|
||||||
|
prepared_params = |
||||||
|
%{} |
||||||
|
|> Map.put("compiler_version", cargo_stylus_version) |
||||||
|
|> Map.put("contract_source_code", contract_source_code) |
||||||
|
|> Map.put("name", contract_name) |
||||||
|
|> Map.put("file_path", contract_source_code && @default_file_name) |
||||||
|
|> Map.put("secondary_sources", secondary_sources) |
||||||
|
|> Map.put("package_name", package_name) |
||||||
|
|> Map.put("github_repository_metadata", github_repository_metadata) |
||||||
|
|
||||||
|
publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string || "null")) |
||||||
|
end |
||||||
|
|
||||||
|
# Stores information about a verified Stylus smart contract in the database. |
||||||
|
# |
||||||
|
# ## Parameters |
||||||
|
# - `address_hash`: The contract's address hash as binary or `t:Explorer.Chain.Hash.t/0` |
||||||
|
# - `params`: Map containing contract details: |
||||||
|
# - `name`: Contract name |
||||||
|
# - `file_path`: Path to the contract source file |
||||||
|
# - `compiler_version`: Version of the Stylus compiler |
||||||
|
# - `contract_source_code`: Source code of the contract |
||||||
|
# - `secondary_sources`: Additional source files |
||||||
|
# - `package_name`: Package name for Stylus contract |
||||||
|
# - `github_repository_metadata`: Repository metadata |
||||||
|
# - `abi`: Contract's ABI (Application Binary Interface) |
||||||
|
# |
||||||
|
# ## Returns |
||||||
|
# - `{:ok, smart_contract}` if publishing succeeds |
||||||
|
# - `{:error, changeset}` if there are validation errors |
||||||
|
# - `{:error, message}` if the database operation fails |
||||||
|
@spec publish_smart_contract(binary() | Explorer.Chain.Hash.t(), %{String.t() => any()}, map()) :: |
||||||
|
{:error, Ecto.Changeset.t() | String.t()} | {:ok, Explorer.Chain.SmartContract.t()} |
||||||
|
defp publish_smart_contract(address_hash, params, abi) do |
||||||
|
attrs = address_hash |> attributes(params, abi) |
||||||
|
|
||||||
|
create_or_update_smart_contract(address_hash, attrs) |
||||||
|
end |
||||||
|
|
||||||
|
# This function first checks if a smart contract already exists in the database |
||||||
|
# at the given address. If it exists, updates the contract with new attributes. |
||||||
|
# Otherwise, creates a new smart contract record. |
||||||
|
@spec create_or_update_smart_contract(binary() | Explorer.Chain.Hash.t(), map()) :: |
||||||
|
{:error, Ecto.Changeset.t() | String.t()} | {:ok, Explorer.Chain.SmartContract.t()} |
||||||
|
defp create_or_update_smart_contract(address_hash, attrs) do |
||||||
|
Logger.info("Publish successfully verified Stylus smart-contract #{address_hash} into the DB") |
||||||
|
|
||||||
|
if SmartContract.verified?(address_hash) do |
||||||
|
SmartContract.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) |
||||||
|
else |
||||||
|
SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
# Creates an invalid changeset for a Stylus smart contract that failed verification. |
||||||
|
# |
||||||
|
# Prepares contract attributes with MD5 hash of bytecode and creates an invalid changeset |
||||||
|
# with appropriate error messages. The changeset is marked with `:insert` action to |
||||||
|
# indicate a failed verification attempt. |
||||||
|
# |
||||||
|
# ## Parameters |
||||||
|
# - `address_hash`: The contract's address hash |
||||||
|
# - `params`: Map containing contract details from verification attempt |
||||||
|
# - `error`: The verification error that occurred |
||||||
|
# - `error_message`: Optional custom error message |
||||||
|
# - `verification_with_files?`: Boolean indicating if verification used source files. |
||||||
|
# Defaults to `false` |
||||||
|
# |
||||||
|
# ## Returns |
||||||
|
# An invalid `t:Ecto.Changeset.t/0` with: |
||||||
|
# - Contract attributes including MD5 hash of bytecode |
||||||
|
# - Error message attached to appropriate field |
||||||
|
# - Action set to `:insert` |
||||||
|
@spec unverified_smart_contract(binary() | Explorer.Chain.Hash.t(), %{String.t() => any()}, any(), any(), boolean()) :: |
||||||
|
Ecto.Changeset.t() |
||||||
|
defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do |
||||||
|
attrs = |
||||||
|
address_hash |
||||||
|
|> attributes(params |> Map.put("compiler_version", params["cargo_stylus_version"])) |
||||||
|
|> Helper.add_contract_code_md5() |
||||||
|
|
||||||
|
changeset = |
||||||
|
SmartContract.invalid_contract_changeset( |
||||||
|
%SmartContract{address_hash: address_hash}, |
||||||
|
attrs, |
||||||
|
error, |
||||||
|
error_message, |
||||||
|
verification_with_files? |
||||||
|
) |
||||||
|
|
||||||
|
Logger.error("Stylus smart-contract verification #{address_hash} failed because of the error #{inspect(error)}") |
||||||
|
|
||||||
|
%{changeset | action: :insert} |
||||||
|
end |
||||||
|
|
||||||
|
defp attributes(address_hash, params, abi \\ %{}) do |
||||||
|
%{ |
||||||
|
address_hash: address_hash, |
||||||
|
name: params["name"], |
||||||
|
file_path: params["file_path"], |
||||||
|
compiler_version: params["compiler_version"], |
||||||
|
evm_version: nil, |
||||||
|
optimization_runs: nil, |
||||||
|
optimization: false, |
||||||
|
contract_source_code: params["contract_source_code"], |
||||||
|
constructor_arguments: nil, |
||||||
|
external_libraries: [], |
||||||
|
secondary_sources: params["secondary_sources"], |
||||||
|
abi: abi, |
||||||
|
verified_via_sourcify: false, |
||||||
|
verified_via_eth_bytecode_db: false, |
||||||
|
verified_via_verifier_alliance: false, |
||||||
|
partially_verified: false, |
||||||
|
is_vyper_contract: false, |
||||||
|
autodetect_constructor_args: false, |
||||||
|
is_yul: false, |
||||||
|
compiler_settings: nil, |
||||||
|
license_type: :none, |
||||||
|
is_blueprint: false, |
||||||
|
language: :stylus_rust, |
||||||
|
package_name: params["package_name"], |
||||||
|
github_repository_metadata: params["github_repository_metadata"] |
||||||
|
} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,72 @@ |
|||||||
|
defmodule Explorer.SmartContract.Stylus.PublisherWorker do |
||||||
|
@moduledoc """ |
||||||
|
Processes Stylus smart contract verification requests asynchronously in the background. |
||||||
|
|
||||||
|
This module implements a worker that handles verification of Stylus smart contracts |
||||||
|
through their GitHub repository source code. It uses a job queue system to: |
||||||
|
- Receive verification requests containing contract address and GitHub details |
||||||
|
- Delegate verification to the Publisher module |
||||||
|
- Broadcast verification results through the events system |
||||||
|
""" |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
use Que.Worker, concurrency: 5 |
||||||
|
|
||||||
|
alias Explorer.Chain.Events.Publisher, as: EventsPublisher |
||||||
|
alias Explorer.SmartContract.Stylus.Publisher |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Processes a Stylus smart contract verification request. |
||||||
|
|
||||||
|
Initiates the verification process by broadcasting the verification request to |
||||||
|
the module responsible for the actual verification and consequent update of |
||||||
|
the database. This function is called automatically by the job queue system. |
||||||
|
|
||||||
|
## Parameters |
||||||
|
- `{"github_repository", params}`: Tuple containing: |
||||||
|
- First element: `"github_repository"` indicating the verification source |
||||||
|
- Second element: Map containing: |
||||||
|
- `"address_hash"`: The contract's address hash to verify |
||||||
|
|
||||||
|
## Returns |
||||||
|
- Result of the broadcast operation |
||||||
|
""" |
||||||
|
@spec perform({binary(), %{String.t() => any()}}) :: any() |
||||||
|
def perform({"github_repository", %{"address_hash" => address_hash} = params}) do |
||||||
|
broadcast(:publish, address_hash, [address_hash, params]) |
||||||
|
end |
||||||
|
|
||||||
|
# Broadcasts the result of a Stylus smart contract verification attempt. |
||||||
|
# |
||||||
|
# Executes the specified verification method in the `Publisher` module and |
||||||
|
# broadcasts the result through the events publisher. |
||||||
|
# |
||||||
|
# ## Parameters |
||||||
|
# - `method`: The verification method to execute |
||||||
|
# - `address_hash`: Contract address |
||||||
|
# - `args`: Arguments to pass to the verification method |
||||||
|
# |
||||||
|
# ## Returns |
||||||
|
# - `{:ok, contract}` if verification succeeds |
||||||
|
# - `{:error, changeset}` if verification fails |
||||||
|
@spec broadcast(atom(), binary() | Explorer.Chain.Hash.t(), any()) :: any() |
||||||
|
defp broadcast(method, address_hash, args) do |
||||||
|
result = |
||||||
|
case apply(Publisher, method, args) do |
||||||
|
{:ok, _contract} = result -> |
||||||
|
result |
||||||
|
|
||||||
|
{:error, changeset} -> |
||||||
|
Logger.error( |
||||||
|
"Stylus smart-contract verification #{address_hash} failed because of the error: #{inspect(changeset)}" |
||||||
|
) |
||||||
|
|
||||||
|
{:error, changeset} |
||||||
|
end |
||||||
|
|
||||||
|
Logger.info("Smart-contract #{address_hash} verification: broadcast verification results") |
||||||
|
|
||||||
|
EventsPublisher.broadcast([{:contract_verification_result, {String.downcase(address_hash), result}}], :on_demand) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,108 @@ |
|||||||
|
defmodule Explorer.SmartContract.Stylus.Verifier do |
||||||
|
@moduledoc """ |
||||||
|
Verifies Stylus smart contracts by comparing their source code against deployed bytecode. |
||||||
|
|
||||||
|
This module handles verification of Stylus smart contracts through their GitHub repository |
||||||
|
source code. It interfaces with a verification microservice that: |
||||||
|
- Fetches source code from the specified GitHub repository and commit |
||||||
|
- Compiles the code using the specified cargo-stylus version |
||||||
|
- Compares the resulting bytecode against the deployed contract bytecode |
||||||
|
- Returns verification details including ABI and contract metadata |
||||||
|
""" |
||||||
|
alias Explorer.Chain.{Hash, SmartContract} |
||||||
|
alias Explorer.SmartContract.StylusVerifierInterface |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Verifies a Stylus smart contract by comparing source code from a GitHub repository against the deployed bytecode using a verification microservice. |
||||||
|
|
||||||
|
## Parameters |
||||||
|
- `address_hash`: Contract address |
||||||
|
- `params`: Map containing verification parameters: |
||||||
|
- `cargo_stylus_version`: Version of cargo-stylus used for deployment |
||||||
|
- `repository_url`: GitHub repository URL containing contract code |
||||||
|
- `commit`: Git commit hash used for deployment |
||||||
|
- `path_prefix`: Optional path prefix if contract is not in repository root |
||||||
|
|
||||||
|
## Returns |
||||||
|
- `{:ok, map}` with verification details: |
||||||
|
- `abi`: Contract ABI (optional) |
||||||
|
- `contract_name`: Contract name (optional) |
||||||
|
- `package_name`: Package name |
||||||
|
- `files`: Map of file paths to contents used in verification |
||||||
|
- `cargo_stylus_version`: Version of cargo-stylus used |
||||||
|
- `github_repository_metadata`: Repository metadata (optional) |
||||||
|
- `{:error, any}` if verification fails or is disabled |
||||||
|
""" |
||||||
|
@spec evaluate_authenticity(EthereumJSONRPC.address() | Hash.Address.t(), map()) :: |
||||||
|
{:ok, map()} | {:error, any()} |
||||||
|
def evaluate_authenticity(address_hash, params) do |
||||||
|
evaluate_authenticity_inner(StylusVerifierInterface.enabled?(), address_hash, params) |
||||||
|
rescue |
||||||
|
exception -> |
||||||
|
Logger.error(fn -> |
||||||
|
[ |
||||||
|
"Error while verifying smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}: ", |
||||||
|
Exception.format(:error, exception, __STACKTRACE__) |
||||||
|
] |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
# Verifies the authenticity of a Stylus smart contract using GitHub repository source code. |
||||||
|
# |
||||||
|
# This function retrieves the contract creation transaction and blockchain RPC endpoint, |
||||||
|
# which together with passed parameters are required by the verification microservice to |
||||||
|
# validate the contract deployment and verify the source code against the deployed |
||||||
|
# bytecode. |
||||||
|
# |
||||||
|
# ## Parameters |
||||||
|
# - `true`: Required boolean flag to proceed with verification |
||||||
|
# - `address_hash`: Contract address |
||||||
|
# - `params`: Map containing verification parameters |
||||||
|
# |
||||||
|
# ## Returns |
||||||
|
# - `{:ok, map}` with verification details including ABI, contract name, and source files |
||||||
|
# - `{:error, any}` if verification fails |
||||||
|
@spec evaluate_authenticity_inner(boolean(), EthereumJSONRPC.address() | Hash.Address.t(), map()) :: |
||||||
|
{:ok, map()} | {:error, any()} |
||||||
|
defp evaluate_authenticity_inner(true, address_hash, params) do |
||||||
|
transaction_hash = fetch_data_for_stylus_verification(address_hash) |
||||||
|
rpc_endpoint = Application.get_env(:explorer, :json_rpc_named_arguments)[:transport_options][:url] |
||||||
|
|
||||||
|
params |
||||||
|
|> Map.take(["cargo_stylus_version", "repository_url", "commit", "path_prefix"]) |
||||||
|
|> Map.put("rpc_endpoint", rpc_endpoint) |
||||||
|
|> Map.put("deployment_transaction", transaction_hash) |
||||||
|
|> StylusVerifierInterface.verify_github_repository() |
||||||
|
end |
||||||
|
|
||||||
|
defp evaluate_authenticity_inner(false, _address_hash, _params) do |
||||||
|
{:error, "Stylus verification is disabled"} |
||||||
|
end |
||||||
|
|
||||||
|
# Retrieves the transaction hash that created a Stylus smart contract. |
||||||
|
|
||||||
|
# Looks up the creation transaction for the given contract address and returns its hash. |
||||||
|
# Checks both regular transactions and internal transactions. |
||||||
|
|
||||||
|
# ## Parameters |
||||||
|
# - `address_hash`: The address hash of the smart contract as a binary or `t:Hash.Address.t/0` |
||||||
|
|
||||||
|
# ## Returns |
||||||
|
# - `t:Hash.t/0` - The transaction hash if found |
||||||
|
# - `nil` - If no creation transaction exists |
||||||
|
@spec fetch_data_for_stylus_verification(binary() | Hash.Address.t()) :: Hash.t() | nil |
||||||
|
defp fetch_data_for_stylus_verification(address_hash) do |
||||||
|
case SmartContract.creation_transaction_with_bytecode(address_hash) do |
||||||
|
%{transaction: transaction} -> |
||||||
|
transaction.hash |
||||||
|
|
||||||
|
%{internal_transaction: internal_transaction} -> |
||||||
|
internal_transaction.transaction_hash |
||||||
|
|
||||||
|
_ -> |
||||||
|
nil |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,165 @@ |
|||||||
|
defmodule Explorer.SmartContract.StylusVerifierInterface do |
||||||
|
@moduledoc """ |
||||||
|
Provides an interface for verifying Stylus smart contracts by interacting with a verification |
||||||
|
microservice. |
||||||
|
|
||||||
|
Handles verification requests for Stylus contracts deployed from GitHub repositories by |
||||||
|
communicating with an external verification service. |
||||||
|
""" |
||||||
|
alias HTTPoison.Response |
||||||
|
require Logger |
||||||
|
|
||||||
|
@post_timeout :timer.minutes(5) |
||||||
|
@request_error_msg "Error while sending request to stylus verification microservice" |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Verifies a Stylus smart contract using source code from a GitHub repository. |
||||||
|
|
||||||
|
Sends verification request to the verification microservice with repository details |
||||||
|
and deployment information. |
||||||
|
|
||||||
|
## Parameters |
||||||
|
- `body`: A map containing: |
||||||
|
- `deployment_transaction`: Transaction hash where contract was deployed |
||||||
|
- `rpc_endpoint`: RPC endpoint URL for the chain |
||||||
|
- `cargo_stylus_version`: Version of cargo-stylus used for deployment |
||||||
|
- `repository_url`: GitHub repository URL containing contract code |
||||||
|
- `commit`: Git commit hash used for deployment |
||||||
|
- `path_prefix`: Optional path prefix if contract is not in repository root |
||||||
|
|
||||||
|
## Returns |
||||||
|
- `{:ok, map}` with verification details: |
||||||
|
- `abi`: Contract ABI (optional) |
||||||
|
- `contract_name`: Contract name (optional) |
||||||
|
- `package_name`: Package name |
||||||
|
- `files`: Map of file paths to contents used in verification |
||||||
|
- `cargo_stylus_version`: Version of cargo-stylus used |
||||||
|
- `github_repository_metadata`: Repository metadata (optional) |
||||||
|
- `{:error, any}` if verification fails |
||||||
|
""" |
||||||
|
@spec verify_github_repository(map()) :: {:ok, map()} | {:error, any()} |
||||||
|
def verify_github_repository( |
||||||
|
%{ |
||||||
|
"deployment_transaction" => _, |
||||||
|
"rpc_endpoint" => _, |
||||||
|
"cargo_stylus_version" => _, |
||||||
|
"repository_url" => _, |
||||||
|
"commit" => _, |
||||||
|
"path_prefix" => _ |
||||||
|
} = body |
||||||
|
) do |
||||||
|
http_post_request(github_repository_verification_url(), body) |
||||||
|
end |
||||||
|
|
||||||
|
@spec http_post_request(String.t(), map()) :: {:ok, map()} | {:error, any()} |
||||||
|
defp 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} -> |
||||||
|
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) |
||||||
|
|
||||||
|
{:error, @request_error_msg} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@spec http_get_request(String.t()) :: {:ok, [String.t()]} | {:error, any()} |
||||||
|
defp 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} -> |
||||||
|
Logger.error(fn -> |
||||||
|
[ |
||||||
|
"Error while sending request to verification microservice url: #{url}: ", |
||||||
|
inspect(error, limit: :infinity, printable_limit: :infinity) |
||||||
|
] |
||||||
|
end) |
||||||
|
|
||||||
|
{:error, @request_error_msg} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Retrieves a list of supported versions of Cargo Stylus package from the verification microservice. |
||||||
|
|
||||||
|
## Returns |
||||||
|
- `{:ok, [String.t()]}` - List of versions on success |
||||||
|
- `{:error, any()}` - Error message if the request fails |
||||||
|
""" |
||||||
|
@spec get_versions_list() :: {:ok, [String.t()]} | {:error, any()} |
||||||
|
def get_versions_list do |
||||||
|
http_get_request(versions_list_url()) |
||||||
|
end |
||||||
|
|
||||||
|
@spec process_verifier_response(binary()) :: {:ok, map() | [String.t()]} | {:error, any()} |
||||||
|
defp process_verifier_response(body) when is_binary(body) do |
||||||
|
case Jason.decode(body) do |
||||||
|
{:ok, decoded} -> |
||||||
|
process_verifier_response(decoded) |
||||||
|
|
||||||
|
_ -> |
||||||
|
{:error, body} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
# Handles response from `stylus-sdk-rs/verify-github-repository` of stylus verifier microservice |
||||||
|
@spec process_verifier_response(map()) :: {:ok, map()} |
||||||
|
defp process_verifier_response(%{"verification_success" => source}) do |
||||||
|
{:ok, source} |
||||||
|
end |
||||||
|
|
||||||
|
# Handles response from `stylus-sdk-rs/verify-github-repository` of stylus verifier microservice |
||||||
|
@spec process_verifier_response(map()) :: {:ok, map()} |
||||||
|
defp process_verifier_response(%{"verificationSuccess" => source}) do |
||||||
|
{:ok, source} |
||||||
|
end |
||||||
|
|
||||||
|
# Handles response from `stylus-sdk-rs/verify-github-repository` of stylus verifier microservice |
||||||
|
@spec process_verifier_response(map()) :: {:error, String.t()} |
||||||
|
defp process_verifier_response(%{"verification_failure" => %{"message" => error_message}}) do |
||||||
|
{:error, error_message} |
||||||
|
end |
||||||
|
|
||||||
|
# Handles response from `stylus-sdk-rs/verify-github-repository` of stylus verifier microservice |
||||||
|
@spec process_verifier_response(map()) :: {:error, String.t()} |
||||||
|
defp process_verifier_response(%{"verificationFailure" => %{"message" => error_message}}) do |
||||||
|
{:error, error_message} |
||||||
|
end |
||||||
|
|
||||||
|
# Handles response from `stylus-sdk-rs/cargo-stylus-versions` of stylus verifier microservice |
||||||
|
@spec process_verifier_response(map()) :: {:ok, [String.t()]} |
||||||
|
defp process_verifier_response(%{"versions" => versions}), do: {:ok, Enum.map(versions, &Map.fetch!(&1, "version"))} |
||||||
|
|
||||||
|
@spec process_verifier_response(any()) :: {:error, any()} |
||||||
|
defp process_verifier_response(other) 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 |
||||||
|
|
||||||
|
defp github_repository_verification_url, |
||||||
|
do: base_api_url() <> "%3Averify-github-repository" |
||||||
|
|
||||||
|
defp versions_list_url, do: base_api_url() <> "/cargo-stylus-versions" |
||||||
|
|
||||||
|
defp base_api_url, do: "#{base_url()}" <> "/api/v1/stylus-sdk-rs" |
||||||
|
|
||||||
|
defp base_url, do: Application.get_env(:explorer, __MODULE__)[:service_url] |
||||||
|
|
||||||
|
def enabled?, |
||||||
|
do: !is_nil(base_url()) && Application.get_env(:explorer, :chain_type) == :arbitrum |
||||||
|
end |
@ -0,0 +1,10 @@ |
|||||||
|
defmodule Explorer.Repo.Arbitrum.Migrations.AddStylusFields do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:smart_contracts) do |
||||||
|
add(:package_name, :string, null: true) |
||||||
|
add(:github_repository_metadata, :jsonb, null: true) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,9 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.AddLanguageField do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:smart_contracts) do |
||||||
|
add(:language, :int2, null: true) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue