From 239597e0937490500d44c98403c0b78de933e1cb Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 29 Sep 2022 00:41:49 +0400 Subject: [PATCH] Filter by time created in listonctracts API endpoint --- CHANGELOG.md | 1 + .../controllers/api/rpc/address_controller.ex | 55 ++--- .../api/rpc/contract_controller.ex | 16 +- .../lib/block_scout_web/etherscan.ex | 30 ++- .../api/rpc/address_controller_test.exs | 76 +++---- .../api/rpc/contract_controller_test.exs | 209 ++++++++++++------ .../lib/explorer/etherscan/contracts.ex | 29 ++- 7 files changed, 261 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 639b9871ea..7fe0dbea36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- [#6187](https://github.com/blockscout/blockscout/pull/6187) - Filter by created time of verified contracts in listcontracts API endpoint - [#6092](https://github.com/blockscout/blockscout/pull/6092) - Blockscout Account functionality - [#6073](https://github.com/blockscout/blockscout/pull/6073) - Add vyper support for rust verifier microservice integration - [#6111](https://github.com/blockscout/blockscout/pull/6111) - Add Prometheus metrics to indexer diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index 54ef51a4e4..394c3018e8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -249,11 +249,11 @@ defmodule BlockScoutWeb.API.RPC.AddressController do %{} |> put_order_by_direction(params) |> Helpers.put_pagination_options(params) - |> put_start_block(params) - |> put_end_block(params) + |> put_block(params, "start_block") + |> put_block(params, "end_block") |> put_filter_by(params) - |> put_start_timestamp(params) - |> put_end_timestamp(params) + |> put_timestamp(params, "start_timestamp") + |> put_timestamp(params, "end_timestamp") end @doc """ @@ -427,52 +427,39 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end end - defp put_start_block(options, params) do - with %{"startblock" => startblock_param} <- params, - {start_block, ""} <- Integer.parse(startblock_param) do - Map.put(options, :start_block, start_block) - else - _ -> - options - end - end - - defp put_end_block(options, params) do - with %{"endblock" => endblock_param} <- params, - {end_block, ""} <- Integer.parse(endblock_param) do - Map.put(options, :end_block, end_block) + # sobelow_skip ["DOS.StringToAtom"] + defp put_block(options, params, key) do + with %{^key => block_param} <- params, + {block_number, ""} <- Integer.parse(block_param) do + Map.put(options, String.to_atom(key), block_number) else _ -> options end end + # sobelow_skip ["DOS.StringToAtom"] defp put_filter_by(options, params) do case params do - %{"filterby" => filter_by} when filter_by in ["from", "to"] -> - Map.put(options, :filter_by, filter_by) + %{"filter_by" => filter_by} when filter_by in ["from", "to"] -> + Map.put(options, String.to_atom("filter_by"), filter_by) _ -> options end end - defp put_start_timestamp(options, params) do - with %{"starttimestamp" => starttimestamp_param} <- params, - {unix_timestamp, ""} <- Integer.parse(starttimestamp_param), - {:ok, start_timestamp} <- DateTime.from_unix(unix_timestamp) do - Map.put(options, :start_timestamp, start_timestamp) - else - _ -> - options - end + def put_timestamp({:ok, options}, params, timestamp_param_key) do + options = put_timestamp(options, params, timestamp_param_key) + {:ok, options} end - defp put_end_timestamp(options, params) do - with %{"endtimestamp" => endtimestamp_param} <- params, - {unix_timestamp, ""} <- Integer.parse(endtimestamp_param), - {:ok, end_timestamp} <- DateTime.from_unix(unix_timestamp) do - Map.put(options, :end_timestamp, end_timestamp) + # sobelow_skip ["DOS.StringToAtom"] + def put_timestamp(options, params, timestamp_param_key) do + with %{^timestamp_param_key => timestamp_param} <- params, + {unix_timestamp, ""} <- Integer.parse(timestamp_param), + {:ok, timestamp} <- DateTime.from_unix(unix_timestamp) do + Map.put(options, String.to_atom(timestamp_param_key), timestamp) else _ -> options diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 4411c28ba2..f2965ad204 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do require Logger alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController - alias BlockScoutWeb.API.RPC.Helpers + alias BlockScoutWeb.API.RPC.{AddressController, Helpers} alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.{Address, Hash, SmartContract} @@ -420,7 +420,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do case Map.get(opts, :filter) do :verified -> - Contracts.list_verified_contracts(page_size, offset) + Contracts.list_verified_contracts(page_size, offset, opts) :decompiled -> not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version) @@ -443,7 +443,9 @@ defmodule BlockScoutWeb.API.RPC.ContractController do defp add_filters(options, params) do options |> add_filter(params) - |> add_not_decompiled_with_version(params) + |> add_param(params, :not_decompiled_with_version) + |> AddressController.put_timestamp(params, "verified_at_start_timestamp") + |> AddressController.put_timestamp(params, "verified_at_end_timestamp") end defp add_filter(options, params) do @@ -456,14 +458,14 @@ 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)} + defp add_param({:ok, options}, params, key) do + case Map.fetch(params, Atom.to_string(key)) do + {:ok, value} -> {:ok, Map.put(options, key, value)} :error -> {:ok, options} end end - defp add_not_decompiled_with_version(options, _params) do + defp add_param(options, _params, _key) do options end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 13e3261bfb..6afc2206f5 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -1406,12 +1406,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to descending order. Available values: asc, desc" }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number." }, @@ -1428,7 +1428,7 @@ defmodule BlockScoutWeb.Etherscan do "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." }, %{ - key: "filterby", + key: "filter_by", type: "string", description: """ A string representing the field to filter by. If none is given @@ -1437,12 +1437,12 @@ defmodule BlockScoutWeb.Etherscan do """ }, %{ - key: "starttimestamp", + key: "start_timestamp", type: "unix timestamp", description: "Represents the starting block timestamp." }, %{ - key: "endtimestamp", + key: "end_timestamp", type: "unix timestamp", description: "Represents the ending block timestamp." } @@ -1499,13 +1499,13 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc. WARNING: Only available if 'address' is provided." }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number. WARNING: Only available if 'address' is provided." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number. WARNING: Only available if 'address' is provided." @@ -1574,12 +1574,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc" }, %{ - key: "startblock", + key: "start_block", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "endblock", + key: "end_block", type: "integer", description: "A nonnegative integer that represents the ending block number." }, @@ -2316,6 +2316,18 @@ defmodule BlockScoutWeb.Etherscan do type: "string", description: "Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts." + }, + %{ + key: "verified_at_start_timestamp", + type: "unix timestamp", + description: + "Represents the starting timestamp when contracts verified. Taking into account only with `verified` filter." + }, + %{ + key: "verified_at_end_timestamp", + type: "unix timestamp", + description: + "Represents the ending timestamp when contracts verified. Taking into account only with `verified` filter." } ], responses: [ diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 7b38784942..1a1b7fa302 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -1090,7 +1090,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with startblock and endblock params", %{conn: conn} do + test "with start_block and end_block params", %{conn: conn} do blocks = [_, second_block, third_block, _] = insert_list(4, :block) address = insert(:address) @@ -1104,8 +1104,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "#{second_block.number}", - "endblock" => "#{third_block.number}" + "start_block" => "#{second_block.number}", + "end_block" => "#{third_block.number}" } expected_block_numbers = [ @@ -1129,7 +1129,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with startblock but without endblock", %{conn: conn} do + test "with start_block but without end_block", %{conn: conn} do blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) address = insert(:address) @@ -1143,7 +1143,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "#{third_block.number}" + "start_block" => "#{third_block.number}" } expected_block_numbers = [ @@ -1167,7 +1167,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with endblock but without startblock", %{conn: conn} do + test "with end_block but without start_block", %{conn: conn} do blocks = [first_block, second_block, _, _] = insert_list(4, :block) address = insert(:address) @@ -1181,7 +1181,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "endblock" => "#{second_block.number}" + "end_block" => "#{second_block.number}" } expected_block_numbers = [ @@ -1205,7 +1205,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "ignores invalid startblock and endblock", %{conn: conn} do + test "ignores invalid start_block and end_block", %{conn: conn} do blocks = [_, _, _, _] = insert_list(4, :block) address = insert(:address) @@ -1219,8 +1219,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "startblock" => "invalidstart", - "endblock" => "invalidend" + "start_block" => "invalidstart", + "end_block" => "invalidend" } assert response = @@ -1234,7 +1234,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with starttimestamp and endtimestamp params", %{conn: conn} do + test "with start_timestamp and end_timestamp params", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1257,8 +1257,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "starttimestamp" => "#{start_timestamp}", - "endtimestamp" => "#{end_timestamp}" + "start_timestamp" => "#{start_timestamp}", + "end_timestamp" => "#{end_timestamp}" } expected_block_numbers = [ @@ -1282,7 +1282,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with starttimestamp but without endtimestamp", %{conn: conn} do + test "with start_timestamp but without end_timestamp", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1304,7 +1304,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "starttimestamp" => "#{start_timestamp}" + "start_timestamp" => "#{start_timestamp}" } expected_block_numbers = [ @@ -1330,7 +1330,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with endtimestamp but without starttimestamp", %{conn: conn} do + test "with end_timestamp but without start_timestamp", %{conn: conn} do now = Timex.now() timestamp1 = Timex.shift(now, hours: -6) timestamp2 = Timex.shift(now, hours: -3) @@ -1352,7 +1352,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "endtimestamp" => "#{end_timestamp}" + "end_timestamp" => "#{end_timestamp}" } expected_block_numbers = [ @@ -1376,7 +1376,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with filterby=to option", %{conn: conn} do + test "with filter_by=to option", %{conn: conn} do block = insert(:block) address = insert(:address) @@ -1390,7 +1390,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "filterby" => "to" + "filter_by" => "to" } assert response = @@ -1404,7 +1404,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with filterby=from option", %{conn: conn} do + test "with filter_by=from option", %{conn: conn} do block = insert(:block) address = insert(:address) @@ -1421,7 +1421,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "filterby" => "from" + "filter_by" => "from" } assert response = @@ -2830,16 +2830,16 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do describe "optional_params/1" do test "includes valid optional params in the required format" do params = %{ - "startblock" => "100", - "endblock" => "120", + "start_block" => "100", + "end_block" => "120", "sort" => "asc", # page number "page" => "1", # page size "offset" => "2", - "filterby" => "to", - "starttimestamp" => "1539186474", - "endtimestamp" => "1539186474" + "filter_by" => "to", + "start_timestamp" => "1539186474", + "end_timestamp" => "1539186474" } optional_params = AddressController.optional_params(params) @@ -2875,20 +2875,20 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert AddressController.optional_params(params3) == %{} end - test "'filterby' value can be 'to' or 'from'" do - params1 = %{"filterby" => "to"} + test "'filter_by' value can be 'to' or 'from'" do + params1 = %{"filter_by" => "to"} optional_params1 = AddressController.optional_params(params1) assert optional_params1.filter_by == "to" - params2 = %{"filterby" => "from"} + params2 = %{"filter_by" => "from"} optional_params2 = AddressController.optional_params(params2) assert optional_params2.filter_by == "from" - params3 = %{"filterby" => "invalid"} + params3 = %{"filter_by" => "invalid"} assert AddressController.optional_params(params3) == %{} end @@ -2899,25 +2899,25 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do test "ignores invalid optional params, keeps valid ones" do params1 = %{ - "startblock" => "invalid", - "endblock" => "invalid", + "start_block" => "invalid", + "end_block" => "invalid", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", - "starttimestamp" => "invalid", - "endtimestamp" => "invalid" + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" } assert AddressController.optional_params(params1) == %{} params2 = %{ - "startblock" => "4", - "endblock" => "10", + "start_block" => "4", + "end_block" => "10", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", - "starttimestamp" => "invalid", - "endtimestamp" => "invalid" + "start_timestamp" => "invalid", + "end_timestamp" => "invalid" } optional_params = AddressController.optional_params(params2) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 6f0c4f6fb0..5c4306b232 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -6,6 +6,66 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do import Mox + def prepare_contracts do + insert(:contract_address) + {:ok, dt_1, _} = DateTime.from_iso8601("2022-09-20 10:00:00Z") + + contract_1 = + insert(:smart_contract, + contract_code_md5: "123", + name: "Test 1", + optimization: "1", + compiler_version: "v0.6.8+commit.0bbfe453", + abi: [%{foo: "bar"}], + inserted_at: dt_1 + ) + + insert(:contract_address) + {:ok, dt_2, _} = DateTime.from_iso8601("2022-09-22 10:00:00Z") + + contract_2 = + insert(:smart_contract, + contract_code_md5: "12345", + name: "Test 2", + optimization: "0", + compiler_version: "v0.7.5+commit.eb77ed08", + abi: [%{foo: "bar-2"}], + inserted_at: dt_2 + ) + + insert(:contract_address) + {:ok, dt_3, _} = DateTime.from_iso8601("2022-09-24 10:00:00Z") + + contract_3 = + insert(:smart_contract, + contract_code_md5: "1234567", + name: "Test 3", + optimization: "1", + compiler_version: "v0.4.26+commit.4563c3fc", + abi: [%{foo: "bar-3"}], + inserted_at: dt_3 + ) + + [contract_1, contract_2, contract_3] + end + + def result(contract) do + %{ + "ABI" => Jason.encode!(contract.abi), + "Address" => to_string(contract.address_hash), + "CompilerVersion" => contract.compiler_version, + "ContractName" => contract.name, + "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") + } + end + + defp result_not_verified(address_hash) do + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(address_hash) + } + end + describe "listcontracts" do setup do %{params: %{"module" => "contract", "action" => "listcontracts"}} @@ -47,15 +107,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => Jason.encode!(contract.abi), - "Address" => to_string(contract.address_hash), - "CompilerVersion" => contract.compiler_version, - "ContractName" => contract.name, - "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") - } - ] + assert response["result"] == [result(contract)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -71,12 +123,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -93,12 +140,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -119,12 +161,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash) - } - ] + assert response["result"] == [result_not_verified(address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -141,15 +178,82 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => Jason.encode!(contract.abi), - "Address" => to_string(contract.address_hash), - "CompilerVersion" => contract.compiler_version, - "ContractName" => contract.name, - "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") - } - ] + assert response["result"] == [result(contract)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts in the date range shows only verified contracts in that range", %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_start_timestamp", "1663749418") + |> Map.put("verified_at_end_timestamp", "1663922218") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_2)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts with start created_at timestamp >= given timestamp shows only verified contracts in that range", + %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_start_timestamp", "1663749418") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_2), result(contract_3)] + + assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) + end + + test "filtering for only verified contracts with end created_at timestamp < given timestamp shows only verified contracts in that range", + %{ + params: params, + conn: conn + } do + [contract_1, contract_2, contract_3] = prepare_contracts() + + filter_params = + params + |> Map.put("filter", "verified") + |> Map.put("verified_at_end_timestamp", "1663922218") + + response = + conn + |> get("/api", filter_params) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [result(contract_1), result(contract_2)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -166,12 +270,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(decompiled_smart_contract.address_hash) - } - ] + assert response["result"] == [result_not_verified(decompiled_smart_contract.address_hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -188,12 +287,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash) - } - ] + assert response["result"] == [result_not_verified(smart_contract.address_hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -212,10 +306,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash) - } in response["result"] + assert result_not_verified(smart_contract.address_hash) in response["result"] refute to_string(non_match.address_hash) in Enum.map(response["result"], &Map.get(&1, "Address")) assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) @@ -234,12 +325,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash) - } - ] + assert response["result"] == [result_not_verified(contract_address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end @@ -261,12 +347,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert response["status"] == "1" - assert response["result"] == [ - %{ - "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash) - } - ] + assert response["result"] == [result_not_verified(contract_address.hash)] assert :ok = ExJsonSchema.Validator.validate(listcontracts_schema(), response) end diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index 37e8ef7a34..a02f791af7 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -7,7 +7,8 @@ defmodule Explorer.Etherscan.Contracts do import Ecto.Query, only: [ - from: 2 + from: 2, + where: 3 ] alias Explorer.{Chain, Repo} @@ -80,7 +81,7 @@ defmodule Explorer.Etherscan.Contracts do def append_proxy_info(address), do: address - def list_verified_contracts(limit, offset) do + def list_verified_contracts(limit, offset, opts) do query = from( smart_contract in SmartContract, @@ -90,7 +91,29 @@ defmodule Explorer.Etherscan.Contracts do preload: [:address] ) - query + verified_at_start_timestamp_exist? = Map.has_key?(opts, :verified_at_start_timestamp) + verified_at_end_timestamp_exist? = Map.has_key?(opts, :verified_at_end_timestamp) + + query_in_timestamp_range = + cond do + verified_at_start_timestamp_exist? && verified_at_end_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at >= ^opts.verified_at_start_timestamp) + |> where([smart_contract], smart_contract.inserted_at < ^opts.verified_at_end_timestamp) + + verified_at_start_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at >= ^opts.verified_at_start_timestamp) + + verified_at_end_timestamp_exist? -> + query + |> where([smart_contract], smart_contract.inserted_at < ^opts.verified_at_end_timestamp) + + true -> + query + end + + query_in_timestamp_range |> Repo.replica().all() |> Enum.map(fn smart_contract -> Map.put(smart_contract.address, :smart_contract, smart_contract)