From f375ca05bb4067a507f6f1883ab8bb4a1316fd2e Mon Sep 17 00:00:00 2001 From: Den Smalonski Date: Mon, 9 Sep 2024 15:27:00 +0200 Subject: [PATCH] feat: verified contracts csv --- .../lib/block_scout_web/api_router.ex | 2 + .../api/v2/smart_contract_controller.ex | 78 +++++++++++++++++++ .../smart_contracts_csv_exporter.ex | 34 ++++++++ .../lib/explorer/chain/smart_contract.ex | 14 ++++ 4 files changed, 128 insertions(+) create mode 100644 apps/explorer/lib/explorer/chain/csv_export/smart_contracts_csv_exporter.ex diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index f29cf2d72b5e..079214bc9cad 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -370,6 +370,8 @@ defmodule BlockScoutWeb.ApiRouter do get("/logs-csv", AddressTransactionController, :logs_csv) + get("/smart-contracts-csv", V2.SmartContractController, :smart_contracts_csv) + scope "/health" do get("/", HealthController, :health) get("/liveness", HealthController, :liveness) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 294cfaab5f1f..3326eb5e36fb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -17,6 +17,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.Solidity.PublishHelper alias Explorer.ThirdPartyIntegrations.SolidityScan + alias Explorer.Chain.CSVExport.SmartContractsCsvExporter + alias Plug.Conn @smart_contract_address_options [ necessity_by_association: %{ @@ -295,6 +297,82 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def prepare_args(list) when is_list(list), do: list def prepare_args(other), do: [other] + def smart_contracts_csv(conn, params) do + items_csv(conn, params, SmartContractsCsvExporter) + end + + defp items_csv( + conn, + %{ + "from_period" => from_period, + "to_period" => to_period, + }, + csv_export_module + ) do + with true <- Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] do + csv_export_module.export(from_period, to_period) + |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn -> + case Conn.chunk(conn, chunk) do + {:ok, conn} -> + {:cont, conn} + + {:error, :closed} -> + {:halt, conn} + end + end) + else + :error -> + unprocessable_entity(conn) + + false -> + not_found(conn) + end + end + + defp items_csv( + conn, + %{ + "from_period" => from_period, + "to_period" => to_period, + "recaptcha_response" => recaptcha_response + }, + csv_export_module + ) do + with {:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do + csv_export_module.export(from_period, to_period) + |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn -> + case Conn.chunk(conn, chunk) do + {:ok, conn} -> + {:cont, conn} + + {:error, :closed} -> + {:halt, conn} + end + end) + else + :error -> + unprocessable_entity(conn) + + {:recaptcha, false} -> + not_found(conn) + end + end + + defp captcha_helper do + :block_scout_web + |> Application.get_env(:captcha_helper) + end + + defp items_csv(conn, _, _), do: not_found(conn) + + defp put_resp_params(conn) do + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment;") + |> put_resp_cookie("csv-downloaded", "true", max_age: 86_400, http_only: false) + |> send_chunked(200) + end + defp validate_smart_contract(params, address_hash_string) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), diff --git a/apps/explorer/lib/explorer/chain/csv_export/smart_contracts_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/smart_contracts_csv_exporter.ex new file mode 100644 index 000000000000..0d6dbe3fd4ca --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/smart_contracts_csv_exporter.ex @@ -0,0 +1,34 @@ +defmodule Explorer.Chain.CSVExport.SmartContractsCsvExporter do + @moduledoc """ + Exports smart contracts to a csv file. + """ + + alias Explorer.Chain.{SmartContract, Address} + alias Explorer.Chain.CSVExport.Helper + + def export(from_period, to_period) do + SmartContract.verified_contracts_by_date_range(from_period, to_period) + |> to_csv_format() + |> Helper.dump_to_stream() + end + + defp to_csv_format(smart_contracts) do + row_names = [ + "contract_address", + "contract_name", + "chain", + ] + chain_id = Application.get_env(:block_scout_web, :chain_id) + smart_contracts_list = + smart_contracts + |> Stream.map(fn smart_contract -> + [ + Address.checksum(smart_contract.address_hash), + smart_contract.name, + chain_id, + ] + end) + + Stream.concat([row_names], smart_contracts_list) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index d8d9eb79b355..a99853b425e1 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -1294,6 +1294,20 @@ defmodule Explorer.Chain.SmartContract do |> Chain.select_repo(options).all() end + def verified_contracts_by_date_range(from_period, to_period) do + query = from(contract in __MODULE__) + date_format = "%Y-%m-%d" + + query + |> where( + [contract], + contract.inserted_at >= ^Timex.parse!(from_period, date_format, :strftime) and + contract.inserted_at <= ^Timex.parse!(to_period, date_format, :strftime) + ) + |> order_by(desc: :id) + |> Chain.select_repo([]).all() + end + defp search_contracts(basic_query, nil), do: basic_query defp search_contracts(basic_query, search_string) do