Refactor Indexer.Token.Fetcher

This refactor aims to remove the responsability of reading functions
from the Smart Contract from Token.Fetcher. Now, we have the module
Explorer.Token.FunctionsReader that is responsible for it.
pull/1078/head
Felipe Renan 6 years ago
parent 528b5ec12b
commit 477de645fb
  1. 147
      apps/indexer/lib/indexer/token/fetcher.ex
  2. 146
      apps/indexer/test/indexer/token/fetcher_test.exs

@ -4,9 +4,9 @@ defmodule Indexer.Token.Fetcher do
""" """
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Hash, Token}
alias Explorer.Chain.Hash.Address alias Explorer.Chain.Hash.Address
alias Explorer.SmartContract.Reader alias Explorer.Chain.Token
alias Explorer.Token.FunctionsReader
alias Indexer.BufferedTask alias Indexer.BufferedTask
@behaviour BufferedTask @behaviour BufferedTask
@ -18,61 +18,6 @@ defmodule Indexer.Token.Fetcher do
task_supervisor: Indexer.Token.TaskSupervisor task_supervisor: Indexer.Token.TaskSupervisor
] ]
@contract_abi [
%{
"constant" => true,
"inputs" => [],
"name" => "name",
"outputs" => [
%{
"name" => "",
"type" => "string"
}
],
"payable" => false,
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "decimals",
"outputs" => [
%{
"name" => "",
"type" => "uint8"
}
],
"payable" => false,
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "totalSupply",
"outputs" => [
%{
"name" => "",
"type" => "uint256"
}
],
"payable" => false,
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "symbol",
"outputs" => [
%{
"name" => "",
"type" => "string"
}
],
"payable" => false,
"type" => "function"
}
]
@doc false @doc false
def child_spec([init_options, gen_server_options]) do def child_spec([init_options, gen_server_options]) do
{state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments) {state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments)
@ -102,10 +47,10 @@ defmodule Indexer.Token.Fetcher do
end end
@impl BufferedTask @impl BufferedTask
def run([token_contract_address], json_rpc_named_arguments) do def run([token_contract_address], _json_rpc_named_arguments) do
case Chain.token_from_address_hash(token_contract_address) do case Chain.token_from_address_hash(token_contract_address) do
{:ok, %Token{cataloged: false} = token} -> {:ok, %Token{cataloged: false} = token} ->
catalog_token(token, json_rpc_named_arguments) catalog_token(token)
{:ok, _} -> {:ok, _} ->
:ok :ok
@ -120,90 +65,12 @@ defmodule Indexer.Token.Fetcher do
BufferedTask.buffer(__MODULE__, token_contract_addresses) BufferedTask.buffer(__MODULE__, token_contract_addresses)
end end
defp catalog_token(%Token{contract_address_hash: contract_address_hash} = token, json_rpc_named_arguments) do defp catalog_token(%Token{contract_address_hash: contract_address_hash} = token) do
contract_functions = %{ contract_functions = FunctionsReader.get_functions_of(contract_address_hash)
"totalSupply" => [],
"decimals" => [],
"name" => [],
"symbol" => []
}
token_contract_results = token_params = Map.put(contract_functions, :cataloged, true)
Reader.query_unverified_contract(
contract_address_hash,
@contract_abi,
contract_functions,
json_rpc_named_arguments: json_rpc_named_arguments
)
token_params = format_token_params(token, token_contract_results)
{:ok, _} = Chain.update_token(token, token_params) {:ok, _} = Chain.update_token(token, token_params)
:ok :ok
end end
def format_token_params(token, token_contract_data) do
token_contract_data =
for {function_name, {:ok, [function_data]}} <- token_contract_data, into: %{} do
{atomized_key(function_name), function_data}
end
token
|> Map.from_struct()
|> Map.put(:cataloged, true)
|> Map.merge(token_contract_data)
|> handle_invalid_strings()
|> handle_large_strings()
end
defp atomized_key("decimals"), do: :decimals
defp atomized_key("name"), do: :name
defp atomized_key("symbol"), do: :symbol
defp atomized_key("totalSupply"), do: :total_supply
# It's a temp fix to store tokens that have names and/or symbols with characters that the database
# doesn't accept. See https://github.com/poanetwork/blockscout/issues/669 for more info.
defp handle_invalid_strings(%{name: name, symbol: symbol, contract_address_hash: contract_address_hash} = token) do
name = handle_invalid_name(name, contract_address_hash)
symbol = handle_invalid_symbol(symbol)
%{token | name: name, symbol: symbol}
end
defp handle_invalid_name(nil, _contract_address_hash), do: nil
defp handle_invalid_name(name, contract_address_hash) do
case String.valid?(name) do
true -> remove_null_bytes(name)
false -> format_according_contract_address_hash(contract_address_hash)
end
end
defp handle_invalid_symbol(symbol) do
case String.valid?(symbol) do
true -> remove_null_bytes(symbol)
false -> nil
end
end
defp format_according_contract_address_hash(contract_address_hash) do
contract_address_hash
|> Hash.to_string()
|> String.slice(0, 6)
end
defp handle_large_strings(%{name: name, symbol: symbol, type: type} = token) do
[name, type, symbol] = Enum.map([name, type, symbol], &handle_large_string/1)
%{token | name: name, symbol: symbol, type: type}
end
defp handle_large_string(nil), do: nil
defp handle_large_string(string), do: handle_large_string(string, byte_size(string))
defp handle_large_string(string, size) when size > 255, do: binary_part(string, 0, 255)
defp handle_large_string(string, _size), do: string
defp remove_null_bytes(string) do
String.replace(string, "\0", "")
end
end end

@ -76,151 +76,5 @@ defmodule Indexer.Token.FetcherTest do
}} = Chain.token_from_address_hash(contract_address_hash) }} = Chain.token_from_address_hash(contract_address_hash)
end end
end end
test "considers the contract address formatted hash when it is an invalid string", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "name",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "symbol",
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003424e540000000000000000000000000000000000000000000000000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, name: "0x0000"}} = Chain.token_from_address_hash(contract_address_hash)
end
end
test "considers the symbol nil when it is an invalid string", %{json_rpc_named_arguments: json_rpc_named_arguments} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "name",
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003424e540000000000000000000000000000000000000000000000000000000000"
},
%{
id: "symbol",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, symbol: nil}} = Chain.token_from_address_hash(contract_address_hash)
end
end
test "considers name as nil when the name is nil", %{json_rpc_named_arguments: json_rpc_named_arguments} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "symbol",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, name: nil}} = Chain.token_from_address_hash(contract_address_hash)
end
end
test "shortens strings larger than 255 characters", %{json_rpc_named_arguments: json_rpc_named_arguments} do
long_token_name_shortened =
"<button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"<%= gettext(\"Toggle navigation\") %>\"> <span class=\"navbar-toggler-icon\"></sp"
token = insert(:token, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "name",
# this is how the token name would come from the blockchain unshortened.
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010c3c627574746f6e20636c6173733d226e61766261722d746f67676c65722220747970653d22627574746f6e2220646174612d746f67676c653d22636f6c6c617073652220646174612d7461726765743d22236e6176626172537570706f72746564436f6e74656e742220617269612d636f6e74726f6c733d226e6176626172537570706f72746564436f6e74656e742220617269612d657870616e6465643d2266616c73652220617269612d6c6162656c3d223c253d20676574746578742822546f67676c65206e617669676174696f6e222920253e223e203c7370616e20636c6173733d226e61766261722d746f67676c65722d69636f6e223e3c2f7370616e3e203c2f627574746f6e3e0000000000000000000000000000000000000000"
}
]}
end
)
assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, name: ^long_token_name_shortened}} =
Chain.token_from_address_hash(contract_address_hash)
end
end
end end
end end

Loading…
Cancel
Save