Skip to content

Commit

Permalink
feat: add not_decompiled_with_version filter
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Apr 2, 2019
1 parent e7187d6 commit 5606ca2
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do

def listcontracts(conn, params) do
with pagination_options <- Helpers.put_pagination_options(%{}, params),
{:params, {:ok, options}} <- {:params, add_filter(pagination_options, params)} do
{:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do
options_with_defaults =
options
|> Map.put_new(:page_number, 0)
Expand Down Expand Up @@ -71,7 +71,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
Chain.list_verified_contracts(page_size, offset)

:decompiled ->
Chain.list_decompiled_contracts(page_size, offset)
not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version)
Chain.list_decompiled_contracts(page_size, offset, not_decompiled_with_version)

:unverified ->
Chain.list_unverified_contracts(page_size, offset)
Expand All @@ -84,6 +85,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end
end

defp add_filters(options, params) do
options
|> add_filter(params)
|> add_not_decompiled_with_version(params)
end

defp add_filter(options, params) do
with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")},
{:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do
Expand All @@ -94,6 +101,17 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end
end

defp add_not_decompiled_with_version({:ok, options}, params) do
case Map.fetch(params, "not_decompiled_with_version") do
{:ok, value} -> {:ok, Map.put(options, :not_decompiled_with_version, value)}
:error -> {:ok, options}
end
end

defp add_not_decompiled_with_version(options, _params) do
options
end

defp contracts_filter(nil), do: {:ok, nil}
defp contracts_filter(1), do: {:ok, :verified}
defp contracts_filter(2), do: {:ok, :decompiled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
def call_controller(conn, controller, action) do
{:ok, controller.call(conn, action)}
rescue
Conn.WrapperError -> :error
Conn.WrapperError ->
:error
end
end
6 changes: 6 additions & 0 deletions apps/block_scout_web/lib/block_scout_web/etherscan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,12 @@ defmodule BlockScoutWeb.Etherscan do
type: "string",
description:
"verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status."
},
%{
key: "not_decompiled_with_version",
type: "string",
description:
"Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts."
}
],
responses: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,27 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end

defp prepare_source_code_contract(contract, _) do
decompiled_smart_contract = latest_decompiled_smart_contract(contract.decompiled_smart_contracts)

%{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"DecompiledSourceCode" => decompiled_source_code(contract.decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(contract.decompiled_smart_contract),
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
end

defp prepare_contract(%Address{hash: hash, smart_contract: nil, decompiled_smart_contract: decompiled_smart_contract}) do
defp prepare_contract(%Address{
hash: hash,
smart_contract: nil,
decompiled_smart_contracts: decompiled_smart_contracts
}) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)

%{
"Address" => to_string(hash),
"SourceCode" => "",
Expand All @@ -64,8 +72,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
defp prepare_contract(%Address{
hash: hash,
smart_contract: %SmartContract{} = contract,
decompiled_smart_contract: decompiled_smart_contract
decompiled_smart_contracts: decompiled_smart_contracts
}) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)

%{
"Address" => to_string(hash),
"SourceCode" => contract.contract_source_code,
Expand All @@ -78,6 +88,12 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
}
end

defp latest_decompiled_smart_contract([]), do: nil

defp latest_decompiled_smart_contract(contracts) do
Enum.max_by(contracts, fn contract -> DateTime.to_unix(contract.inserted_at) end)
end

defp decompiled_source_code(nil), do: "Contract source code not decompiled."

defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,60 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
]
end

test "filtering for only decompiled contracts, with a decompiled with version filter", %{params: params, conn: conn} do
insert(:decompiled_smart_contract, decompiler_version: "foobar")
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")

response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)

assert response["message"] == "OK"
assert response["status"] == "1"

assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end

test "filtering for only decompiled contracts, with a decompiled with version filter, where another decompiled version exists",
%{params: params, conn: conn} do
non_match = insert(:decompiled_smart_contract, decompiler_version: "foobar")
insert(:decompiled_smart_contract, decompiler_version: "bizbuz", address_hash: non_match.address_hash)
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")

response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)

assert response["message"] == "OK"
assert response["status"] == "1"

assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end

test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do
insert(:decompiled_smart_contract)
insert(:smart_contract)
Expand Down
42 changes: 33 additions & 9 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2666,19 +2666,38 @@ defmodule Explorer.Chain do
Repo.all(query, timeout: :infinity)
end

def list_decompiled_contracts(limit, offset) do
def list_decompiled_contracts(limit, offset, not_decompiled_with_version \\ nil) do
query =
from(
address in Address,
join: decompiled_smart_contract in DecompiledSmartContract,
on: decompiled_smart_contract.address_hash == address.hash,
preload: [{:decompiled_smart_contract, decompiled_smart_contract}, :smart_contract],
where:
fragment(
"EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)",
address.hash
),
preload: [:decompiled_smart_contracts, :smart_contract],
order_by: [asc: address.inserted_at],
limit: ^limit,
offset: ^offset
)

Repo.all(query)
query
|> filter_decompiled_with_version(not_decompiled_with_version)
|> Repo.all()
end

defp filter_decompiled_with_version(query, nil) do
query
end

defp filter_decompiled_with_version(query, not_decompiled_with_version) do
from(address in query,
left_join: decompiled_smart_contract in DecompiledSmartContract,
on: decompiled_smart_contract.decompiler_version == ^not_decompiled_with_version,
on: decompiled_smart_contract.address_hash == address.hash,
where: is_nil(decompiled_smart_contract.id),
distinct: [address.hash]
)
end

def list_verified_contracts(limit, offset) do
Expand All @@ -2688,7 +2707,7 @@ defmodule Explorer.Chain do
where: not is_nil(address.contract_code),
join: smart_contract in SmartContract,
on: smart_contract.address_hash == address.hash,
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract],
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at],
limit: ^limit,
offset: ^offset
Expand All @@ -2702,7 +2721,7 @@ defmodule Explorer.Chain do
from(
address in Address,
where: not is_nil(address.contract_code),
preload: [:smart_contract, :decompiled_smart_contract],
preload: [:smart_contract, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at],
limit: ^limit,
offset: ^offset
Expand All @@ -2719,7 +2738,7 @@ defmodule Explorer.Chain do
on: smart_contract.address_hash == address.hash,
where: not is_nil(address.contract_code),
where: is_nil(smart_contract.address_hash),
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract],
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at],
limit: ^limit,
offset: ^offset
Expand All @@ -2732,11 +2751,16 @@ defmodule Explorer.Chain do
query =
from(
address in Address,
where:
fragment(
"NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)",
address.hash
),
left_join: smart_contract in SmartContract,
on: smart_contract.address_hash == address.hash,
left_join: decompiled_smart_contract in DecompiledSmartContract,
on: decompiled_smart_contract.address_hash == address.hash,
preload: [smart_contract: smart_contract, decompiled_smart_contract: decompiled_smart_contract],
preload: [:smart_contract, :decompiled_smart_contracts],
where: not is_nil(address.contract_code),
where: is_nil(smart_contract.address_hash),
where: is_nil(decompiled_smart_contract.address_hash),
Expand Down
1 change: 0 additions & 1 deletion apps/explorer/lib/explorer/chain/address.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ defmodule Explorer.Chain.Address do
field(:has_decompiled_code?, :boolean, virtual: true)

has_one(:smart_contract, SmartContract)
has_one(:decompiled_smart_contract, DecompiledSmartContract)
has_one(:token, Token, foreign_key: :contract_address_hash)

has_one(
Expand Down
6 changes: 3 additions & 3 deletions apps/explorer/lib/explorer/chain/smart_contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ defmodule Explorer.Chain.SmartContract do
field(:constructor_arguments, :string)
field(:abi, {:array, :map})

has_one(
:decompiled_smart_contract,
has_many(
:decompiled_smart_contracts,
DecompiledSmartContract,
foreign_key: :address_hash
)
Expand All @@ -227,7 +227,7 @@ defmodule Explorer.Chain.SmartContract do
end

def preload_decompiled_smart_contract(contract) do
Repo.preload(contract, :decompiled_smart_contract)
Repo.preload(contract, :decompiled_smart_contracts)
end

def changeset(%__MODULE__{} = smart_contract, attrs) do
Expand Down

0 comments on commit 5606ca2

Please sign in to comment.