Merge pull request #6168 from blockscout/improve-token-balances-updating

Update token balances in token instance fetcher
pull/6191/head
Victor Baranov 2 years ago committed by GitHub
commit cabfdfa6b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 76
      apps/explorer/lib/explorer/token/instance_owner_reader.ex
  3. 57
      apps/indexer/lib/indexer/fetcher/token_instance.ex
  4. 87
      apps/indexer/test/indexer/fetcher/token_instance_test.exs

@ -6,6 +6,7 @@
- [#6092](https://github.com/blockscout/blockscout/pull/6092) - Blockscout Account functionality - [#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 - [#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 - [#6111](https://github.com/blockscout/blockscout/pull/6111) - Add Prometheus metrics to indexer
- [#6168](https://github.com/blockscout/blockscout/pull/6168) - Token instance fetcher checks instance owner and updates current token balance
### Fixes ### Fixes

@ -0,0 +1,76 @@
defmodule Explorer.Token.InstanceOwnerReader do
@moduledoc """
Reads Token Instance owner using Smart Contract function from the blockchain.
"""
require Logger
alias Explorer.SmartContract.Reader
@owner_function_signature "6352211e"
@owner_function_abi [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [
%{
"type" => "address",
"name" => "owner"
}
],
"name" => "ownerOf",
"inputs" => [
%{
"type" => "uint256",
"name" => "tokenId"
}
]
}
]
@spec get_owner_of([%{token_contract_address_hash: String.t(), token_id: integer}]) :: [
{:ok, String.t()} | {:error, String.t()}
]
def get_owner_of(instance_owner_requests) do
instance_owner_requests
|> Enum.map(&format_owner_request/1)
|> Reader.query_contracts(@owner_function_abi)
|> Enum.zip(instance_owner_requests)
|> Enum.reduce([], fn {result, request}, acc ->
case format_owner_result(result, request) do
{:ok, ok_result} ->
[ok_result] ++ acc
{:error, error_message} ->
Logger.error(
"Failed to get owner of token #{request.token_contract_address_hash}, token_id #{request.token_id}, reason: #{error_message}"
)
acc
end
end)
end
defp format_owner_request(%{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do
%{
contract_address: token_contract_address_hash,
method_id: @owner_function_signature,
args: [token_id]
}
end
defp format_owner_result({:ok, [owner]}, request) do
{:ok,
%{
token_contract_address_hash: request.token_contract_address_hash,
token_id: request.token_id,
owner: owner
}}
end
defp format_owner_result({:error, error_message}, _request) do
{:error, error_message}
end
end

@ -8,8 +8,9 @@ defmodule Indexer.Fetcher.TokenInstance do
require Logger require Logger
alias Explorer.Chain alias Explorer.{Chain, Repo}
alias Explorer.Token.InstanceMetadataRetriever alias Explorer.Chain.{Address, Cache.BlockNumber, Token}
alias Explorer.Token.{InstanceMetadataRetriever, InstanceOwnerReader}
alias Indexer.BufferedTask alias Indexer.BufferedTask
@behaviour BufferedTask @behaviour BufferedTask
@ -59,6 +60,7 @@ defmodule Indexer.Fetcher.TokenInstance do
end end
Enum.each(all_token_ids, &fetch_instance(hash, &1)) Enum.each(all_token_ids, &fetch_instance(hash, &1))
update_current_token_balances(hash, all_token_ids)
:ok :ok
end end
@ -97,6 +99,57 @@ defmodule Indexer.Fetcher.TokenInstance do
end end
end end
defp update_current_token_balances(token_contract_address_hash, token_ids) do
token_ids
|> Enum.map(&instance_owner_request(token_contract_address_hash, &1))
|> InstanceOwnerReader.get_owner_of()
|> Enum.map(&current_token_balances_import_params/1)
|> all_import_params()
|> Chain.import()
end
defp instance_owner_request(token_contract_address_hash, token_id) do
%{
token_contract_address_hash: to_string(token_contract_address_hash),
token_id: Decimal.to_integer(token_id)
}
end
defp current_token_balances_import_params(%{token_contract_address_hash: hash, token_id: token_id, owner: owner}) do
%{
value: Decimal.new(1),
block_number: BlockNumber.get_max(),
value_fetched_at: DateTime.utc_now(),
token_id: token_id,
token_type: Repo.get_by(Token, contract_address_hash: hash).type,
address_hash: owner,
token_contract_address_hash: hash
}
end
defp all_import_params(balances_import_params) do
addresses_import_params =
balances_import_params
|> Enum.reduce([], fn %{address_hash: address_hash}, acc ->
case Repo.get_by(Address, hash: address_hash) do
nil -> [%{hash: address_hash} | acc]
_address -> acc
end
end)
|> case do
[] -> %{}
params -> %{addresses: %{params: params}}
end
current_token_balances_import_params = %{
address_current_token_balances: %{
params: balances_import_params
}
}
Map.merge(current_token_balances_import_params, addresses_import_params)
end
@doc """ @doc """
Fetches token instance data asynchronously. Fetches token instance data asynchronously.
""" """

@ -0,0 +1,87 @@
defmodule Indexer.Fetcher.TokenInstanceTest do
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import Mox
alias Explorer.Chain
alias Explorer.Chain.Address
alias Explorer.Chain.Address.CurrentTokenBalance
alias Explorer.Repo
alias Indexer.Fetcher.TokenInstance
describe "run/2" do
test "updates current token balance" do
token = insert(:token, type: "ERC-1155")
token_contract_address_hash = token.contract_address_hash
instance = insert(:token_instance, token_contract_address_hash: token_contract_address_hash)
token_id = instance.token_id
address = insert(:address, hash: "0x57e93bb58268de818b42e3795c97bad58afcd3fe")
address_hash = address.hash
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: 0, method: "eth_call", params: [%{data: "0xc87b56dd" <> _}, _]}], _ ->
{:ok,
[
%{
id: 0,
result:
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000027b7d000000000000000000000000000000000000000000000000000000000000"
}
]}
end)
|> expect(:json_rpc, fn [%{id: 0, method: "eth_call", params: [%{data: "0x6352211e" <> _}, _]}], _ ->
{:ok, [%{id: 0, result: "0x00000000000000000000000057e93bb58268de818b42e3795c97bad58afcd3fe"}]}
end)
TokenInstance.run(
[%{contract_address_hash: token_contract_address_hash, token_id: nil, token_ids: [token_id]}],
nil
)
assert %{
token_id: ^token_id,
token_type: "ERC-1155",
token_contract_address_hash: ^token_contract_address_hash,
address_hash: ^address_hash
} = Repo.one(CurrentTokenBalance)
end
test "updates current token balance with missing address" do
token = insert(:token, type: "ERC-1155")
token_contract_address_hash = token.contract_address_hash
instance = insert(:token_instance, token_contract_address_hash: token_contract_address_hash)
token_id = instance.token_id
{:ok, address_hash} = Chain.string_to_address_hash("0x57e93bb58268de818b42e3795c97bad58afcd3fe")
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: 0, method: "eth_call", params: [%{data: "0xc87b56dd" <> _}, _]}], _ ->
{:ok,
[
%{
id: 0,
result:
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000027b7d000000000000000000000000000000000000000000000000000000000000"
}
]}
end)
|> expect(:json_rpc, fn [%{id: 0, method: "eth_call", params: [%{data: "0x6352211e" <> _}, _]}], _ ->
{:ok, [%{id: 0, result: "0x00000000000000000000000057e93bb58268de818b42e3795c97bad58afcd3fe"}]}
end)
TokenInstance.run(
[%{contract_address_hash: token_contract_address_hash, token_id: token_id, token_ids: nil}],
nil
)
assert %{
token_id: ^token_id,
token_type: "ERC-1155",
token_contract_address_hash: ^token_contract_address_hash,
address_hash: ^address_hash
} = Repo.one(CurrentTokenBalance)
assert %Address{} = Repo.get_by(Address, hash: address_hash)
end
end
end
Loading…
Cancel
Save