Merge pull request #6168 from blockscout/improve-token-balances-updating
Update token balances in token instance fetcherpull/6191/head
commit
cabfdfa6b1
@ -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 |
@ -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…
Reference in new issue