Skip to content

Commit

Permalink
feat: Certified smart contracts (blockscout#9910)
Browse files Browse the repository at this point in the history
* Certified smart-contracts

* Prioritize certified smart-contracts in the search

* Refactoring: remove CustomContractsHelper

* Return certified in the list and in the search

* mix format

* Fix tests

* Process review comment
  • Loading branch information
vbaranov authored Apr 19, 2024
1 parent 942df21 commit cef3285
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,25 @@ defmodule BlockScoutWeb.API.V2.SearchView do
"total_supply" => search_result.total_supply,
"circulating_market_cap" =>
search_result.circulating_market_cap && to_string(search_result.circulating_market_cap),
"is_verified_via_admin_panel" => search_result.is_verified_via_admin_panel
"is_verified_via_admin_panel" => search_result.is_verified_via_admin_panel,
"certified" => if(search_result.certified, do: search_result.certified, else: false)
}
end

def prepare_search_result(%{type: "contract"} = search_result) do
%{
"type" => search_result.type,
"name" => search_result.name,
"address" => search_result.address_hash,
"url" => address_path(Endpoint, :show, search_result.address_hash),
"is_smart_contract_verified" => search_result.verified,
"ens_info" => search_result[:ens_info],
"certified" => if(search_result.certified, do: search_result.certified, else: false)
}
end

def prepare_search_result(%{type: address_or_contract_or_label} = search_result)
when address_or_contract_or_label in ["address", "contract", "label"] do
when address_or_contract_or_label in ["address", "label"] do
%{
"type" => search_result.type,
"name" => search_result.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
do: format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments)
),
"language" => smart_contract_language(smart_contract),
"license_type" => smart_contract.license_type
"license_type" => smart_contract.license_type,
"certified" => if(smart_contract.certified, do: smart_contract.certified, else: false)
}
|> Map.merge(bytecode_info(address))
end
Expand Down Expand Up @@ -326,7 +327,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
"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
"license_type" => smart_contract.license_type,
"certified" => if(smart_contract.certified, do: smart_contract.certified, else: false)
}
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
"license_type" => "none"
"license_type" => "none",
"certified" => false
}

get_eip1967_implementation_non_zero_address()
Expand Down Expand Up @@ -226,7 +227,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
"license_type" => "gnu_agpl_v3"
"license_type" => "gnu_agpl_v3",
"certified" => false
}

get_eip1967_implementation_error_response()
Expand Down Expand Up @@ -330,7 +332,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
"license_type" => "none"
"license_type" => "none",
"certified" => false
}

get_eip1967_implementation_error_response()
Expand Down Expand Up @@ -450,7 +453,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => implementation_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(implementation_contract),
"license_type" => "bsd_3_clause"
"license_type" => "bsd_3_clause",
"certified" => false
}

request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}")
Expand Down
2 changes: 2 additions & 0 deletions apps/explorer/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: :ti

config :explorer, Explorer.Tags.AddressTag.Cataloger, enabled: true

config :explorer, Explorer.SmartContract.CertifiedSmartContractCataloger, enabled: true

config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime_usec]

config :explorer, Explorer.Tracer,
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/lib/explorer/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ defmodule Explorer.Application do
configure(Explorer.Counters.Transactions24hStats),
configure(Explorer.Validator.MetadataProcessor),
configure(Explorer.Tags.AddressTag.Cataloger),
configure(Explorer.SmartContract.CertifiedSmartContractCataloger),
configure(MinMissingBlockNumber),
configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand),
configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand),
Expand Down
4 changes: 4 additions & 0 deletions apps/explorer/lib/explorer/chain/search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ defmodule Explorer.Chain.Search do
from(items in subquery(query),
order_by: [
desc: items.priority,
desc_nulls_last: items.certified,
desc_nulls_last: items.circulating_market_cap,
desc_nulls_last: items.exchange_rate,
desc_nulls_last: items.is_verified_via_admin_panel,
Expand Down Expand Up @@ -324,6 +325,7 @@ defmodule Explorer.Chain.Search do
|> Map.put(:icon_url, dynamic([token, _], token.icon_url))
|> Map.put(:token_type, dynamic([token, _], token.type))
|> Map.put(:verified, dynamic([_, smart_contract], not is_nil(smart_contract)))
|> Map.put(:certified, dynamic([_, smart_contract], smart_contract.certified))
|> Map.put(:exchange_rate, dynamic([token, _], token.fiat_value))
|> Map.put(:total_supply, dynamic([token, _], token.total_supply))
|> Map.put(:circulating_market_cap, dynamic([token, _], token.circulating_market_cap))
Expand Down Expand Up @@ -355,6 +357,7 @@ defmodule Explorer.Chain.Search do
|> Map.put(:type, "contract")
|> Map.put(:name, dynamic([smart_contract, _], smart_contract.name))
|> Map.put(:inserted_at, dynamic([_, address], address.inserted_at))
|> Map.put(:certified, dynamic([smart_contract, _], smart_contract.certified))
|> Map.put(:verified, true)

from(smart_contract in SmartContract,
Expand Down Expand Up @@ -635,6 +638,7 @@ defmodule Explorer.Chain.Search do
token_type: nil,
timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)),
verified: nil,
certified: nil,
exchange_rate: nil,
total_supply: nil,
circulating_market_cap: nil,
Expand Down
28 changes: 26 additions & 2 deletions apps/explorer/lib/explorer/chain/smart_contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ defmodule Explorer.Chain.SmartContract do
* `implementation_address_hash` - address hash of the proxy's implementation if any
* `autodetect_constructor_args` - field was added for storing user's choice
* `is_yul` - field was added for storing user's choice
* `certified` - boolean flag, which can be set for set of smart-contracts via runtime env variable to prioritize those smart-contracts in the search.
"""
typed_schema "smart_contracts" do
field(:name, :string, null: false)
Expand Down Expand Up @@ -305,6 +306,7 @@ defmodule Explorer.Chain.SmartContract do
field(:is_yul, :boolean, virtual: true)
field(:metadata_from_verified_twin, :boolean, virtual: true)
field(:license_type, Ecto.Enum, values: @license_enum, default: :none)
field(:certified, :boolean)

has_many(
:decompiled_smart_contracts,
Expand Down Expand Up @@ -358,7 +360,8 @@ defmodule Explorer.Chain.SmartContract do
:compiler_settings,
:implementation_address_hash,
:implementation_fetched_at,
:license_type
:license_type,
:certified
])
|> validate_required([
:name,
Expand Down Expand Up @@ -401,7 +404,8 @@ defmodule Explorer.Chain.SmartContract do
:contract_code_md5,
:implementation_name,
:autodetect_constructor_args,
:license_type
:license_type,
:certified
])
|> (&if(verification_with_files?,
do: &1,
Expand Down Expand Up @@ -1279,6 +1283,26 @@ defmodule Explorer.Chain.SmartContract do
end
end

@doc """
Sets smart-contract certified flag
"""
@spec set_smart_contracts_certified_flag(list()) ::
{:ok, []} | {:error, String.t()}
def set_smart_contracts_certified_flag([]), do: {:ok, []}

def set_smart_contracts_certified_flag(address_hashes) do
query =
from(
contract in __MODULE__,
where: contract.address_hash in ^address_hashes
)

case Repo.update_all(query, set: [certified: true]) do
{1, _} -> {:ok, []}
_ -> {:error, "There was an error in setting certified flag."}
end
end

defp check_verified_with_full_match(address_hash, options) do
smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options)

Expand Down
6 changes: 1 addition & 5 deletions apps/explorer/lib/explorer/custom_contracts_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Explorer.CustomContractsHelper do
"""

def get_custom_addresses_list(env_var) do
addresses_var = get_raw_custom_addresses_list(env_var)
addresses_var = Application.get_env(:block_scout_web, env_var)
addresses_list = (addresses_var && String.split(addresses_var, ",")) || []

formatted_addresses_list =
Expand All @@ -15,8 +15,4 @@ defmodule Explorer.CustomContractsHelper do

formatted_addresses_list
end

def get_raw_custom_addresses_list(env_var) do
Application.get_env(:block_scout_web, env_var)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Explorer.SmartContract.CertifiedSmartContractCataloger do
@moduledoc """
Actualizes certified smart-contracts.
"""

use GenServer, restart: :transient

alias Explorer.Chain.SmartContract

def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end

@impl GenServer
def init(args) do
send(self(), :fetch_certified_smart_contracts)

{:ok, args}
end

@impl GenServer
def handle_info(:fetch_certified_smart_contracts, state) do
certified_contracts_list = Application.get_env(:block_scout_web, :contract)[:certified_list]

SmartContract.set_smart_contracts_certified_flag(certified_contracts_list)

{:noreply, state}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Explorer.Repo.Migrations.SmartContractsAddCertifiedFlag do
use Ecto.Migration
@disable_ddl_transaction true
@disable_migration_lock true

def change do
alter table("smart_contracts") do
add(:certified, :boolean, null: true)
end

create_if_not_exists(index(:smart_contracts, [:certified]))
end
end
19 changes: 19 additions & 0 deletions config/config_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,25 @@ defmodule ConfigHelper do
err -> raise "Invalid JSON in environment variable #{env_var}: #{inspect(err)}"
end

@spec parse_list_env_var(String.t(), String.t() | nil) :: list()
def parse_list_env_var(env_var, default_value \\ nil) do
addresses_var = safe_get_env(env_var, default_value)

if addresses_var !== "" do
addresses_list = (addresses_var && String.split(addresses_var, ",")) || []

formatted_addresses_list =
addresses_list
|> Enum.map(fn addr ->
String.downcase(addr)
end)

formatted_addresses_list
else
[]
end
end

@supported_chain_types [
"default",
"arbitrum",
Expand Down
3 changes: 2 additions & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ config :block_scout_web, :footer,
config :block_scout_web, :contract,
verification_max_libraries: ConfigHelper.parse_integer_env_var("CONTRACT_VERIFICATION_MAX_LIBRARIES", 10),
max_length_to_show_string_without_trimming: System.get_env("CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING", "2040"),
disable_interaction: ConfigHelper.parse_bool_env_var("CONTRACT_DISABLE_INTERACTION")
disable_interaction: ConfigHelper.parse_bool_env_var("CONTRACT_DISABLE_INTERACTION"),
certified_list: ConfigHelper.parse_list_env_var("CONTRACT_CERTIFIED_LIST", "")

default_global_api_rate_limit = 50
default_api_rate_limit_by_key = 10
Expand Down
1 change: 1 addition & 0 deletions docker-compose/envs/common-blockscout.env
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040
# CONTRACT_DISABLE_INTERACTION=
# CONTRACT_AUDIT_REPORTS_AIRTABLE_URL=
# CONTRACT_AUDIT_REPORTS_AIRTABLE_API_KEY=
# CONTRACT_CERTIFIED_LIST=
UNCLES_IN_AVERAGE_BLOCK_TIME=false
DISABLE_WEBAPP=false
API_V2_ENABLED=true
Expand Down

0 comments on commit cef3285

Please sign in to comment.