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