|
|
|
@ -1,7 +1,9 @@ |
|
|
|
|
defmodule BlockScoutWeb.API.V2.AddressControllerTest do |
|
|
|
|
use BlockScoutWeb.ConnCase |
|
|
|
|
use EthereumJSONRPC.Case, async: false |
|
|
|
|
use BlockScoutWeb.ChannelCase |
|
|
|
|
|
|
|
|
|
alias ABI.{TypeDecoder, TypeEncoder} |
|
|
|
|
alias BlockScoutWeb.Models.UserFromAuth |
|
|
|
|
alias Explorer.{Chain, Repo} |
|
|
|
|
alias Explorer.Chain.Address.Counters |
|
|
|
@ -1835,7 +1837,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do |
|
|
|
|
) |
|
|
|
|
|> Repo.preload([:token]) |
|
|
|
|
end |
|
|
|
|
|> Enum.sort_by(fn x -> x.value end, :asc) |
|
|
|
|
|> Enum.sort_by(fn x -> Decimal.to_integer(x.value) end, :asc) |
|
|
|
|
|
|
|
|
|
ctbs_erc_1155 = |
|
|
|
|
for _ <- 0..50 do |
|
|
|
@ -1846,7 +1848,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do |
|
|
|
|
) |
|
|
|
|
|> Repo.preload([:token]) |
|
|
|
|
end |
|
|
|
|
|> Enum.sort_by(fn x -> x.value end, :asc) |
|
|
|
|
|> Enum.sort_by(fn x -> Decimal.to_integer(x.value) end, :asc) |
|
|
|
|
|
|
|
|
|
filter = %{"type" => "ERC-20"} |
|
|
|
|
request = get(conn, "/api/v2/addresses/#{address.hash}/tokens", filter) |
|
|
|
@ -1883,6 +1885,291 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
describe "checks TokenBalanceOnDemand" do |
|
|
|
|
setup do |
|
|
|
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) |
|
|
|
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) |
|
|
|
|
old_env = Application.get_env(:indexer, Indexer.Fetcher.TokenBalanceOnDemand) |
|
|
|
|
|
|
|
|
|
Application.put_env( |
|
|
|
|
:indexer, |
|
|
|
|
Indexer.Fetcher.TokenBalanceOnDemand, |
|
|
|
|
Keyword.put(old_env, :fallback_threshold_in_blocks, 0) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
on_exit(fn -> |
|
|
|
|
Application.put_env(:indexer, Indexer.Fetcher.TokenBalanceOnDemand, old_env) |
|
|
|
|
end) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
test "Indexer.Fetcher.TokenBalanceOnDemand broadcasts only updated balances", %{conn: conn} do |
|
|
|
|
address = insert(:address) |
|
|
|
|
|
|
|
|
|
ctbs_erc_20 = |
|
|
|
|
for i <- 0..1 do |
|
|
|
|
ctb = |
|
|
|
|
insert(:address_current_token_balance_with_token_id_and_fixed_token_type, |
|
|
|
|
address: address, |
|
|
|
|
token_type: "ERC-20", |
|
|
|
|
token_id: nil |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
{to_string(ctb.token_contract_address_hash), |
|
|
|
|
Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} |
|
|
|
|
end |
|
|
|
|
|> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
ctbs_erc_721 = |
|
|
|
|
for i <- 0..1 do |
|
|
|
|
ctb = |
|
|
|
|
insert(:address_current_token_balance_with_token_id_and_fixed_token_type, |
|
|
|
|
address: address, |
|
|
|
|
token_type: "ERC-721", |
|
|
|
|
token_id: nil |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
{to_string(ctb.token_contract_address_hash), |
|
|
|
|
Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} |
|
|
|
|
end |
|
|
|
|
|> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
other_balances = Map.merge(ctbs_erc_20, ctbs_erc_721) |
|
|
|
|
|
|
|
|
|
balances_erc_1155 = |
|
|
|
|
for i <- 0..1 do |
|
|
|
|
ctb = |
|
|
|
|
insert(:address_current_token_balance_with_token_id_and_fixed_token_type, |
|
|
|
|
address: address, |
|
|
|
|
token_type: "ERC-1155", |
|
|
|
|
token_id: Enum.random(1..100_000) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
{{to_string(ctb.token_contract_address_hash), to_string(ctb.token_id)}, |
|
|
|
|
Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} |
|
|
|
|
end |
|
|
|
|
|> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
block_number_hex = "0x" <> (Integer.to_string(insert(:block).number, 16) |> String.upcase()) |
|
|
|
|
|
|
|
|
|
expect(EthereumJSONRPC.Mox, :json_rpc, fn [ |
|
|
|
|
%{ |
|
|
|
|
id: id_1, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x00fdd58e" <> request_1, |
|
|
|
|
to: contract_address_1 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_2, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x00fdd58e" <> request_2, |
|
|
|
|
to: contract_address_2 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
], |
|
|
|
|
_options -> |
|
|
|
|
types_list = [:address, {:uint, 256}] |
|
|
|
|
|
|
|
|
|
[address_1, token_id_1] = request_1 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) |
|
|
|
|
|
|
|
|
|
assert address_1 == address.hash.bytes |
|
|
|
|
|
|
|
|
|
result_1 = |
|
|
|
|
balances_erc_1155[{contract_address_1 |> String.downcase(), to_string(token_id_1)}] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
[address_2, token_id_2] = request_2 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) |
|
|
|
|
|
|
|
|
|
assert address_2 == address.hash.bytes |
|
|
|
|
|
|
|
|
|
result_2 = |
|
|
|
|
balances_erc_1155[{contract_address_2 |> String.downcase(), to_string(token_id_2)}] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
{:ok, |
|
|
|
|
[ |
|
|
|
|
%{ |
|
|
|
|
id: id_1, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_1 |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_2, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_2 |
|
|
|
|
} |
|
|
|
|
]} |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
expect(EthereumJSONRPC.Mox, :json_rpc, fn [ |
|
|
|
|
%{ |
|
|
|
|
id: id_1, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x70a08231" <> request_1, |
|
|
|
|
to: contract_address_1 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_2, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x70a08231" <> request_2, |
|
|
|
|
to: contract_address_2 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_3, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x70a08231" <> request_3, |
|
|
|
|
to: contract_address_3 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_4, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
method: "eth_call", |
|
|
|
|
params: [ |
|
|
|
|
%{ |
|
|
|
|
data: "0x70a08231" <> request_4, |
|
|
|
|
to: contract_address_4 |
|
|
|
|
}, |
|
|
|
|
^block_number_hex |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
], |
|
|
|
|
_options -> |
|
|
|
|
types_list = [:address] |
|
|
|
|
|
|
|
|
|
assert request_1 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] |
|
|
|
|
|
|
|
|
|
assert request_2 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] |
|
|
|
|
|
|
|
|
|
assert request_3 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] |
|
|
|
|
|
|
|
|
|
assert request_4 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] |
|
|
|
|
|
|
|
|
|
result_1 = |
|
|
|
|
other_balances[contract_address_1 |> String.downcase()] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
result_2 = |
|
|
|
|
other_balances[contract_address_2 |> String.downcase()] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
result_3 = |
|
|
|
|
other_balances[contract_address_3 |> String.downcase()] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
result_4 = |
|
|
|
|
other_balances[contract_address_4 |> String.downcase()] |
|
|
|
|
|> List.wrap() |
|
|
|
|
|> TypeEncoder.encode_raw([{:uint, 256}], :standard) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|
|
|
|
|
{:ok, |
|
|
|
|
[ |
|
|
|
|
%{ |
|
|
|
|
id: id_1, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_1 |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_2, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_2 |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_3, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_3 |
|
|
|
|
}, |
|
|
|
|
%{ |
|
|
|
|
id: id_4, |
|
|
|
|
jsonrpc: "2.0", |
|
|
|
|
result: "0x" <> result_4 |
|
|
|
|
} |
|
|
|
|
]} |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
topic = "addresses:#{address.hash}" |
|
|
|
|
|
|
|
|
|
{:ok, _reply, _socket} = |
|
|
|
|
BlockScoutWeb.UserSocketV2 |
|
|
|
|
|> socket("no_id", %{}) |
|
|
|
|
|> subscribe_and_join(topic) |
|
|
|
|
|
|
|
|
|
request = get(conn, "/api/v2/addresses/#{address.hash}/tokens") |
|
|
|
|
assert _response = json_response(request, 200) |
|
|
|
|
overflow = false |
|
|
|
|
|
|
|
|
|
assert_receive %Phoenix.Socket.Message{ |
|
|
|
|
payload: %{token_balances: [ctb_erc_20], overflow: ^overflow}, |
|
|
|
|
event: "updated_token_balances_erc_20", |
|
|
|
|
topic: ^topic |
|
|
|
|
}, |
|
|
|
|
:timer.seconds(1) |
|
|
|
|
|
|
|
|
|
assert_receive %Phoenix.Socket.Message{ |
|
|
|
|
payload: %{token_balances: [ctb_erc_721], overflow: ^overflow}, |
|
|
|
|
event: "updated_token_balances_erc_721", |
|
|
|
|
topic: ^topic |
|
|
|
|
}, |
|
|
|
|
:timer.seconds(1) |
|
|
|
|
|
|
|
|
|
assert_receive %Phoenix.Socket.Message{ |
|
|
|
|
payload: %{token_balances: [ctb_erc_1155], overflow: ^overflow}, |
|
|
|
|
event: "updated_token_balances_erc_1155", |
|
|
|
|
topic: ^topic |
|
|
|
|
}, |
|
|
|
|
:timer.seconds(1) |
|
|
|
|
|
|
|
|
|
assert Decimal.to_integer(ctb_erc_20["value"]) == |
|
|
|
|
other_balances[ctb_erc_20["token"]["address"] |> String.downcase()] |
|
|
|
|
|
|
|
|
|
assert Decimal.to_integer(ctb_erc_721["value"]) == |
|
|
|
|
other_balances[ctb_erc_721["token"]["address"] |> String.downcase()] |
|
|
|
|
|
|
|
|
|
assert Decimal.to_integer(ctb_erc_1155["value"]) == |
|
|
|
|
balances_erc_1155[ |
|
|
|
|
{ctb_erc_1155["token"]["address"] |> String.downcase(), to_string(ctb_erc_1155["token_id"])} |
|
|
|
|
] |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
describe "/addresses/{address_hash}/withdrawals" do |
|
|
|
|
test "get empty list on non existing address", %{conn: conn} do |
|
|
|
|
address = build(:address) |
|
|
|
|