Merge pull request #6712 from blockscout/np-core-bs-api-9

API v2: new methods
pull/6728/head
Victor Baranov 2 years ago committed by GitHub
commit 308ef02d93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 5
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  3. 44
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  4. 11
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex
  5. 8
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
  6. 1
      apps/block_scout_web/lib/block_scout_web/paging_helper.ex
  7. 5
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  8. 21
      apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
  9. 7
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  10. 30
      apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
  11. 165
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  12. 100
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
  13. 15
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  14. 10
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
  15. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
  16. 44
      apps/explorer/lib/explorer/chain.ex
  17. 7
      apps/explorer/lib/explorer/chain/token_transfer.ex
  18. 13
      apps/explorer/lib/explorer/chain/transaction.ex

@ -4,6 +4,8 @@
### Features
- [#6712](https://github.com/blockscout/blockscout/pull/6712) - API v2 update
### Fixes
- [#6705](https://github.com/blockscout/blockscout/pull/6705) - Fix `/smart-contracts` bugs in API v2

@ -98,7 +98,10 @@ defmodule BlockScoutWeb.ApiRouter do
alias BlockScoutWeb.API.V2
get("/search", V2.SearchController, :search)
scope "/search" do
get("/", V2.SearchController, :search)
get("/check-redirect", V2.SearchController, :check_redirect)
end
scope "/config" do
get("/json-rpc-url", V2.ConfigController, :json_rpc_url)

@ -34,7 +34,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
necessity_by_association: %{
:to_address => :optional,
:from_address => :optional,
:block => :optional
:block => :optional,
:transaction => :optional
}
]
@ -113,6 +114,47 @@ defmodule BlockScoutWeb.API.V2.AddressController do
end
end
def token_transfers(
conn,
%{"address_hash" => address_hash_string, "token" => token_address_hash_string} = params
) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:format, {:ok, token_address_hash}} <- {:format, Chain.string_to_address_hash(token_address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:ok, false} <- AccessHelpers.restricted_access?(token_address_hash_string, params),
{:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, [], false)},
{:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(token_address_hash)} do
options =
[
necessity_by_association: %{
:to_address => :optional,
:from_address => :optional,
:block => :optional,
:token => :optional,
:transaction => :optional
}
]
|> Keyword.merge(paging_options(params))
results_plus_one =
Chain.address_hash_to_token_transfers_by_token_address_hash(
address_hash,
token_address_hash,
options
)
{token_transfers, next_page} = split_list_by_page(results_plus_one)
next_page_params =
next_page |> next_page_params(token_transfers, params) |> delete_parameters_from_next_page_params()
conn
|> put_status(200)
|> put_view(TransactionView)
|> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params})
end
end
def token_transfers(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),

@ -21,4 +21,15 @@ defmodule BlockScoutWeb.API.V2.SearchController do
|> put_status(200)
|> render(:search_results, %{search_results: search_results, next_page_params: next_page_params})
end
def check_redirect(conn, %{"q" => query}) do
result =
query
|> String.trim()
|> BlockScoutWeb.Chain.from_param()
conn
|> put_status(200)
|> render(:search_results, %{result: result})
end
end

@ -6,7 +6,9 @@ defmodule BlockScoutWeb.API.V2.TokenController do
alias Explorer.{Chain, Market}
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1]
import BlockScoutWeb.PagingHelper,
only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1]
action_fallback(BlockScoutWeb.API.V2.FallbackController)
@ -32,7 +34,8 @@ defmodule BlockScoutWeb.API.V2.TokenController do
def transfers(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(address_hash)} do
results_plus_one = Chain.fetch_token_transfers_from_token_hash(address_hash, paging_options(params))
{token_transfers, next_page} = split_list_by_page(results_plus_one)
@ -77,6 +80,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do
paging_params =
params
|> paging_options()
|> Keyword.merge(token_transfers_types_options(params))
{tokens, next_page} = filter |> Chain.list_top_tokens(paging_params) |> Market.add_price() |> split_list_by_page()

@ -133,6 +133,7 @@ defmodule BlockScoutWeb.PagingHelper do
|> Map.delete("type")
|> Map.delete("method")
|> Map.delete("filter")
|> Map.delete("token_address_hash")
end
def delete_parameters_from_next_page_params(_), do: nil

@ -51,7 +51,10 @@ defmodule BlockScoutWeb.API.V2.AddressView do
end
def prepare_address({address, nonce}) do
nil |> Helper.address_with_info(address, address.hash) |> Map.put(:tx_count, to_string(nonce))
nil
|> Helper.address_with_info(address, address.hash)
|> Map.put(:tx_count, to_string(nonce))
|> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value))
end
def prepare_address(address, conn \\ nil) do

@ -2,11 +2,20 @@ defmodule BlockScoutWeb.API.V2.SearchView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Endpoint
alias Explorer.Chain.{Address, Block, Transaction}
def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do
%{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params}
end
def render("search_results.json", %{result: {:ok, result}}) do
Map.merge(%{"redirect" => true}, redirect_search_results(result))
end
def render("search_results.json", %{result: {:error, :not_found}}) do
%{"redirect" => false, "type" => nil, "parameter" => nil}
end
def prepare_search_result(%{type: "token"} = search_result) do
%{
"type" => search_result.type,
@ -50,4 +59,16 @@ defmodule BlockScoutWeb.API.V2.SearchView do
end
defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower)
defp redirect_search_results(%Address{} = item) do
%{"type" => "address", "parameter" => Address.checksum(item.hash)}
end
defp redirect_search_results(%Block{} = item) do
%{"type" => "block", "parameter" => to_string(item.hash)}
end
defp redirect_search_results(%Transaction{} = item) do
%{"type" => "transaction", "parameter" => to_string(item.hash)}
end
end

@ -129,7 +129,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
target_contract = if smart_contract_verified, do: address.smart_contract, else: metadata_for_verification
%{
"verified_twin_address_hash" => metadata_for_verification && metadata_for_verification.address_hash,
"verified_twin_address_hash" =>
metadata_for_verification && Address.checksum(metadata_for_verification.address_hash),
"is_verified" => smart_contract_verified,
"is_changed_bytecode" => smart_contract_verified && address.smart_contract.is_changed_bytecode,
"is_partially_verified" => address.smart_contract.partially_verified && smart_contract_verified,
@ -189,7 +190,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
defp prepare_external_libraries(libraries) when is_list(libraries) do
Enum.map(libraries, fn %Explorer.Chain.SmartContract.ExternalLibrary{name: name, address_hash: address_hash} ->
%{name: name, address_hash: address_hash}
{:ok, hash} = Chain.string_to_address_hash(address_hash)
%{name: name, address_hash: Address.checksum(hash)}
end)
end

@ -82,6 +82,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
def prepare_token_transfer(token_transfer, conn) do
decoded_input = token_transfer.transaction |> Transaction.decoded_input_data() |> format_decoded_input()
%{
"tx_hash" => token_transfer.transaction_hash,
"from" => Helper.address_with_info(conn, token_transfer.from_address, token_transfer.from_address_hash),
@ -89,7 +91,14 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
"total" => prepare_token_transfer_total(token_transfer),
"token" => TokenView.render("token.json", %{token: Market.add_price(token_transfer.token)}),
"type" => Chain.get_token_transfer_type(token_transfer),
"timestamp" => block_timestamp(token_transfer.block)
"timestamp" =>
if(match?(%NotLoaded{}, token_transfer.block),
do: block_timestamp(token_transfer.transaction),
else: block_timestamp(token_transfer.block)
),
"method" => method_name(token_transfer.transaction, decoded_input, true),
"block_hash" => to_string(token_transfer.block_hash),
"log_index" => to_string(token_transfer.log_index)
}
end
@ -145,6 +154,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
decoded = decode_log(log, transaction_or_hash)
%{
"tx_hash" => get_tx_hash(transaction_or_hash),
"address" => Helper.address_with_info(log.address, log.address_hash),
"topics" => [
log.first_topic,
@ -159,6 +169,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
}
end
defp get_tx_hash(%Transaction{} = tx), do: to_string(tx.hash)
defp get_tx_hash(hash), do: to_string(hash)
defp smart_contract_info(%Transaction{} = tx), do: Helper.address_with_info(tx.to_address, tx.to_address_hash)
defp smart_contract_info(_), do: nil
@ -371,19 +384,25 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
|> Timex.diff(right, :milliseconds)
end
defp method_name(_, {:ok, _method_id, text, _mapping}) do
defp method_name(_, _, skip_sc_check? \\ false)
defp method_name(_, {:ok, _method_id, text, _mapping}, _) do
Transaction.parse_method_name(text, false)
end
defp method_name(%Transaction{to_address: to_address, input: %{bytes: <<method_id::binary-size(4), _::binary>>}}, _) do
if Helper.is_smart_contract(to_address) do
defp method_name(
%Transaction{to_address: to_address, input: %{bytes: <<method_id::binary-size(4), _::binary>>}},
_,
skip_sc_check?
) do
if Helper.is_smart_contract(to_address) || skip_sc_check? do
"0x" <> Base.encode16(method_id, case: :lower)
else
nil
end
end
defp method_name(_, _) do
defp method_name(_, _, _) do
nil
end
@ -445,6 +464,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
end
end
defp block_timestamp(%Transaction{block: %Block{} = block}), do: block.timestamp
defp block_timestamp(%Block{} = block), do: block.timestamp
defp block_timestamp(_), do: nil
end

@ -7,6 +7,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
Address,
Address.CoinBalance,
Block,
Data,
InternalTransaction,
Log,
Token,
@ -36,7 +37,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
correct_reponse = %{
"hash" => Address.checksum(address.hash),
"implementation_name" => nil,
"is_contract" => false,
"is_verified" => false,
"name" => nil,
@ -340,7 +340,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
end
describe "/addresses/{address_hash}/token-transfers" do
test "get empty list on non existing address", %{conn: conn} do
test "get 404 on non existing address", %{conn: conn} do
address = build(:address)
request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers")
@ -354,10 +354,69 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422)
end
test "get 404 on non existing address of token", %{conn: conn} do
address = insert(:address)
token = build(:address)
request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"token" => to_string(token.hash)})
assert %{"message" => "Not found"} = json_response(request, 404)
end
test "get 422 on invalid token address hash", %{conn: conn} do
address = insert(:address)
request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"token" => "0x"})
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422)
end
test "get relevant token transfer", %{conn: conn} do
address = insert(:address)
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number)
token_transfer =
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers")
assert response = json_response(request, 200)
assert Enum.count(response["items"]) == 1
assert response["next_page_params"] == nil
compare_item(token_transfer, Enum.at(response["items"], 0))
end
test "method in token transer could be decoded", %{conn: conn} do
insert(:contract_method,
identifier: Base.decode16!("731133e9", case: :lower),
abi: %{
"constant" => false,
"inputs" => [
%{"name" => "account", "type" => "address"},
%{"name" => "id", "type" => "uint256"},
%{"name" => "amount", "type" => "uint256"},
%{"name" => "data", "type" => "bytes"}
],
"name" => "mint",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
}
)
address = insert(:address)
tx =
insert(:transaction,
input:
"0x731133e9000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000"
)
|> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number)
@ -370,12 +429,76 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert Enum.count(response["items"]) == 1
assert response["next_page_params"] == nil
compare_item(token_transfer, Enum.at(response["items"], 0))
assert Enum.at(response["items"], 0)["method"] == "mint"
end
test "get relevant token transfer filtered by token", %{conn: conn} do
token = insert(:token)
address = insert(:address)
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number)
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
token_transfer =
insert(:token_transfer,
transaction: tx,
block: tx.block,
block_number: tx.block_number,
from_address: address,
token_contract_address: token.contract_address
)
request =
get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{
"token" => to_string(token.contract_address)
})
assert response = json_response(request, 200)
assert Enum.count(response["items"]) == 1
assert response["next_page_params"] == nil
compare_item(token_transfer, Enum.at(response["items"], 0))
end
test "token transfers by token can paginate", %{conn: conn} do
address = insert(:address)
token = insert(:token)
token_tranfers =
for _ <- 0..50 do
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
insert(:token_transfer,
transaction: tx,
block: tx.block,
block_number: tx.block_number,
from_address: address,
token_contract_address: token.contract_address
)
end
params = %{"token" => to_string(token.contract_address)}
request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", params)
assert response = json_response(request, 200)
request_2nd_page =
get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", Map.merge(params, response["next_page_params"]))
assert response_2nd_page = json_response(request_2nd_page, 200)
check_paginated_response(response, response_2nd_page, token_tranfers)
end
test "get only :to token transfer", %{conn: conn} do
address = insert(:address)
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
@ -393,7 +516,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
test "get only :from token transfer", %{conn: conn} do
address = insert(:address)
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
token_transfer =
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
@ -412,7 +535,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
token_tranfers =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
end
@ -430,13 +553,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
address = insert(:address)
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
end
token_tranfers =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address)
end
@ -457,13 +580,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
token_tranfers =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
end
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address)
end
@ -484,14 +607,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
tt_from =
for _ <- 0..49 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address)
end
tt_to =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address)
end
@ -525,7 +648,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
erc_20_tt =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -540,7 +663,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
erc_721_tt =
for x <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -556,7 +679,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
erc_1155_tt =
for x <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -650,7 +773,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
erc_20_tt =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -665,7 +788,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
erc_721_tt =
for x <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -1235,6 +1358,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert response_2nd_page = json_response(request_2nd_page, 200)
check_paginated_response(response, response_2nd_page, addresses)
assert Enum.at(response["items"], 0)["coin_balance"] ==
to_string(Enum.at(addresses, 50).fetched_coin_balance.value)
end
test "check nil", %{conn: conn} do
@ -1265,6 +1391,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
assert json["timestamp"] != nil
assert json["method"] != nil
assert to_string(token_transfer.block_hash) == json["block_hash"]
assert to_string(token_transfer.log_index) == json["log_index"]
end
defp compare_item(%InternalTransaction{} = internal_tx, json) do
@ -1309,6 +1439,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert log.index == json["index"]
assert to_string(log.data) == json["data"]
assert Address.checksum(log.address_hash) == json["address"]["hash"]
assert to_string(log.transaction_hash) == json["tx_hash"]
end
defp check_paginated_response(first_page_resp, second_page_resp, list) do

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.API.V2.SearchControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.Address
alias Explorer.Chain.{Address, Block}
setup do
insert(:block)
@ -144,4 +144,102 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
assert item["url"] =~ to_string(tx.hash)
end
end
describe "/search/check-redirect" do
test "finds a consensus block by block number", %{conn: conn} do
block = insert(:block)
hash = to_string(block.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{block.number}")
assert %{"redirect" => true, "type" => "block", "parameter" => ^hash} = json_response(request, 200)
end
test "redirects to search results page even for searching non-consensus block by number", %{conn: conn} do
%Block{number: number} = insert(:block, consensus: false)
request = get(conn, "/api/v2/search/check-redirect?q=#{number}")
%{"redirect" => false, "type" => nil, "parameter" => nil} = json_response(request, 200)
end
test "finds non-consensus block by hash", %{conn: conn} do
%Block{hash: hash} = insert(:block, consensus: false)
conn = get(conn, "/search?q=#{hash}")
hash = to_string(hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{hash}")
assert %{"redirect" => true, "type" => "block", "parameter" => ^hash} = json_response(request, 200)
end
test "finds a transaction by hash", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
hash = to_string(transaction.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{hash}")
assert %{"redirect" => true, "type" => "transaction", "parameter" => ^hash} = json_response(request, 200)
end
test "finds a transaction by hash when there are not 0x prefix", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
hash = to_string(transaction.hash)
"0x" <> non_prefix_hash = to_string(transaction.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{non_prefix_hash}")
assert %{"redirect" => true, "type" => "transaction", "parameter" => ^hash} = json_response(request, 200)
end
test "finds an address by hash", %{conn: conn} do
address = insert(:address)
hash = Address.checksum(address.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{to_string(address.hash)}")
assert %{"redirect" => true, "type" => "address", "parameter" => ^hash} = json_response(request, 200)
end
test "finds an address by hash when there are extra spaces", %{conn: conn} do
address = insert(:address)
hash = Address.checksum(address.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{to_string(address.hash)} ")
assert %{"redirect" => true, "type" => "address", "parameter" => ^hash} = json_response(request, 200)
end
test "finds an address by hash when there are not 0x prefix", %{conn: conn} do
address = insert(:address)
"0x" <> non_prefix_hash = to_string(address.hash)
hash = Address.checksum(address.hash)
request = get(conn, "/api/v2/search/check-redirect?q=#{non_prefix_hash}")
assert %{"redirect" => true, "type" => "address", "parameter" => ^hash} = json_response(request, 200)
end
test "redirects to result page when it finds nothing", %{conn: conn} do
request = get(conn, "/api/v2/search/check-redirect?q=qwerty")
%{"redirect" => false, "type" => nil, "parameter" => nil} = json_response(request, 200)
end
end
end

@ -55,7 +55,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
test "get smart-contract", %{conn: conn} do
lib_address_string = to_string(build(:address))
lib_address = build(:address)
lib_address_string = to_string(lib_address)
target_contract =
insert(:smart_contract,
@ -95,7 +96,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"file_path" => target_contract.file_path,
"additional_sources" => [],
"compiler_settings" => target_contract.compiler_settings,
"external_libraries" => [%{"name" => "ABC", "address_hash" => lib_address_string}],
"external_libraries" => [%{"name" => "ABC", "address_hash" => Address.checksum(lib_address)}],
"constructor_args" => target_contract.constructor_arguments,
"decoded_constructor_args" => nil,
"is_self_destructed" => false,
@ -114,7 +115,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
test "get smart-contract with decoded constructor", %{conn: conn} do
lib_address_string = to_string(build(:address))
lib_address = build(:address)
lib_address_string = to_string(lib_address)
target_contract =
insert(:smart_contract,
@ -181,7 +183,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"file_path" => target_contract.file_path,
"additional_sources" => [],
"compiler_settings" => target_contract.compiler_settings,
"external_libraries" => [%{"name" => "ABC", "address_hash" => lib_address_string}],
"external_libraries" => [%{"name" => "ABC", "address_hash" => Address.checksum(lib_address)}],
"constructor_args" => target_contract.constructor_arguments,
"decoded_constructor_args" => [
["0x0000000000000000000000000000000000000000", %{"name" => "_proxyStorage", "type" => "address"}],
@ -203,7 +205,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
test "get smart-contract data from twin without constructor args", %{conn: conn} do
lib_address_string = to_string(build(:address))
lib_address = build(:address)
lib_address_string = to_string(lib_address)
target_contract =
insert(:smart_contract,
@ -276,7 +279,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"file_path" => target_contract.file_path,
"additional_sources" => [],
"compiler_settings" => target_contract.compiler_settings,
"external_libraries" => [%{"name" => "ABC", "address_hash" => lib_address_string}],
"external_libraries" => [%{"name" => "ABC", "address_hash" => Address.checksum(lib_address)}],
"constructor_args" => nil,
"decoded_constructor_args" => nil,
"is_self_destructed" => false,

@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
token_contract_address: contract_token_address
)
second_page_token_balances =
_second_page_token_balances =
1..5
|> Enum.map(
&insert(
@ -100,7 +100,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/transfers")
assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200)
assert %{"message" => "Not found"} = json_response(request, 404)
end
test "get 422 on invalid address", %{conn: conn} do
@ -122,7 +122,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
token_tranfers =
for _ <- 0..50 do
tx = insert(:transaction) |> with_block()
tx = insert(:transaction, input: "0xabcd010203040506") |> with_block()
insert(:token_transfer,
transaction: tx,
@ -243,6 +243,10 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
assert json["timestamp"] != nil
assert json["method"] != nil
assert to_string(token_transfer.block_hash) == json["block_hash"]
assert to_string(token_transfer.log_index) == json["log_index"]
end
def compare_item(%CurrentTokenBalance{} = ctb, json) do

@ -555,12 +555,15 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
assert to_string(log.data) == json["data"]
assert log.index == json["index"]
assert Address.checksum(log.address_hash) == json["address"]["hash"]
assert to_string(log.transaction_hash) == json["tx_hash"]
end
defp compare_item(%TokenTransfer{} = token_transfer, json) do
assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"]
assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"]
assert to_string(token_transfer.transaction_hash) == json["tx_hash"]
assert to_string(token_transfer.block_hash) == json["block_hash"]
assert to_string(token_transfer.log_index) == json["log_index"]
end
defp check_paginated_response(first_page_resp, second_page_resp, txs) do

@ -583,6 +583,23 @@ defmodule Explorer.Chain do
|> Repo.all()
end
@spec address_hash_to_token_transfers_by_token_address_hash(
Hash.Address.t() | String.t(),
Hash.Address.t() | String.t(),
Keyword.t()
) :: [TokenTransfer.t()]
def address_hash_to_token_transfers_by_token_address_hash(address_hash, token_address_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
necessity_by_association = Keyword.get(options, :necessity_by_association)
address_hash
|> TokenTransfer.token_transfers_by_address_hash_and_token_address_hash(token_address_hash)
|> join_associations(necessity_by_association)
|> TokenTransfer.handle_paging_options(paging_options)
|> Repo.all()
end
@doc """
address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract).
It is used by CSV export of token transfers button.
@ -2459,17 +2476,13 @@ defmodule Explorer.Chain do
@spec list_top_tokens(String.t()) :: [{Token.t(), non_neg_integer()}]
def list_top_tokens(filter, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
token_type = Keyword.get(options, :token_type, nil)
fetch_top_tokens(filter, paging_options)
fetch_top_tokens(filter, paging_options, token_type)
end
defp fetch_top_tokens(filter, paging_options) do
base_query =
from(t in Token,
where: t.total_supply > ^0,
order_by: [desc_nulls_last: t.holder_count, asc: t.name],
preload: [:contract_address]
)
defp fetch_top_tokens(filter, paging_options, token_type) do
base_query = base_token_query(token_type)
base_query_with_paging =
base_query
@ -2494,6 +2507,21 @@ defmodule Explorer.Chain do
|> Repo.all()
end
defp base_token_query(empty_type) when empty_type in [nil, []] do
from(t in Token,
order_by: [desc_nulls_last: t.holder_count, asc: t.name],
preload: [:contract_address]
)
end
defp base_token_query(token_types) when is_list(token_types) do
from(t in Token,
where: t.type in ^token_types,
order_by: [desc_nulls_last: t.holder_count, asc: t.name],
preload: [:contract_address]
)
end
@doc """
Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`.
"""

@ -315,6 +315,13 @@ defmodule Explorer.Chain.TokenTransfer do
)
end
def token_transfers_by_address_hash_and_token_address_hash(address_hash, token_address_hash) do
TokenTransfer
|> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash)
|> where([tt], tt.token_contract_address_hash == ^token_address_hash)
|> order_by([tt], desc: tt.block_number, desc: tt.log_index)
end
def token_transfers_by_address_hash(direction, address_hash, token_types) do
TokenTransfer
|> filter_by_direction(direction, address_hash)

@ -463,6 +463,7 @@ defmodule Explorer.Chain.Transaction do
# Because there is no contract association, we know the contract was not verified
def decoded_input_data(%__MODULE__{to_address: nil}), do: {:error, :no_to_address}
def decoded_input_data(%NotLoaded{}), do: {:error, :not_loaded}
def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}) when bytes in [nil, <<>>], do: {:error, :no_input_data}
def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}), do: {:error, :not_a_contract_call}
@ -478,6 +479,18 @@ defmodule Explorer.Chain.Transaction do
})
end
def decoded_input_data(%__MODULE__{
to_address: %NotLoaded{},
input: input,
hash: hash
}) do
decoded_input_data(%__MODULE__{
to_address: %{smart_contract: nil},
input: input,
hash: hash
})
end
def decoded_input_data(%__MODULE__{
to_address: %{smart_contract: nil},
input: %{bytes: <<method_id::binary-size(4), _::binary>> = data},

Loading…
Cancel
Save