Blockchain explorer for Ethereum based network and a tool for inspecting and analyzing EVM based blockchains.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
blockscout/apps/explorer/test/explorer/chain_test.exs

5588 lines
187 KiB

defmodule Explorer.ChainTest do
use Explorer.DataCase
use EthereumJSONRPC.Case, async: true
require Ecto.Query
import Ecto.Query
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Explorer.Factory
import Mox
alias Explorer.{Chain, Factory, PagingOptions, Repo}
alias Explorer.Chain.{
Address,
Block,
Data,
DecompiledSmartContract,
Hash,
InternalTransaction,
Log,
PendingBlockOperation,
Token,
TokenTransfer,
Transaction,
SmartContract,
Wei
}
alias Explorer.Chain
alias Explorer.Chain.InternalTransaction.Type
alias Explorer.Chain.Supply.ProofOfAuthority
alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Counters.AddressesCounter
doctest Explorer.Chain
setup :set_mox_global
setup :verify_on_exit!
describe "remove_nonconsensus_blocks_from_pending_ops/0" do
test "removes pending ops for nonconsensus blocks" do
block = insert(:block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
nonconsensus_block = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true)
:ok = Chain.remove_nonconsensus_blocks_from_pending_ops()
assert Repo.get(PendingBlockOperation, block.hash)
assert is_nil(Repo.get(PendingBlockOperation, nonconsensus_block.hash))
end
test "removes pending ops for nonconsensus blocks by block hashes" do
block = insert(:block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
nonconsensus_block = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true)
nonconsensus_block1 = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block1, fetch_internal_transactions: true)
:ok = Chain.remove_nonconsensus_blocks_from_pending_ops([nonconsensus_block1.hash])
assert Repo.get(PendingBlockOperation, block.hash)
assert Repo.get(PendingBlockOperation, nonconsensus_block.hash)
assert is_nil(Repo.get(PendingBlockOperation, nonconsensus_block1.hash))
end
end
describe "count_addresses_with_balance_from_cache/0" do
test "returns the number of addresses with fetched_coin_balance > 0" do
insert(:address, fetched_coin_balance: 0)
insert(:address, fetched_coin_balance: 1)
insert(:address, fetched_coin_balance: 2)
start_supervised!(AddressesWithBalanceCounter)
AddressesWithBalanceCounter.consolidate()
addresses_with_balance = Chain.count_addresses_with_balance_from_cache()
assert is_integer(addresses_with_balance)
assert addresses_with_balance == 2
end
end
describe "address_estimated_count/0" do
test "returns the number of all addresses" do
insert(:address, fetched_coin_balance: 0)
insert(:address, fetched_coin_balance: 1)
insert(:address, fetched_coin_balance: 2)
start_supervised!(AddressesCounter)
AddressesCounter.consolidate()
addresses_with_balance = Chain.address_estimated_count()
assert is_integer(addresses_with_balance)
assert addresses_with_balance == 3
end
end
describe "last_db_block_status/0" do
test "return no_blocks errors if db is empty" do
assert {:error, :no_blocks} = Chain.last_db_block_status()
end
test "returns {:ok, last_block_period} if block is in healthy period" do
insert(:block, consensus: true)
assert {:ok, _, _} = Chain.last_db_block_status()
end
test "return {:ok, last_block_period} if block is not in healthy period" do
insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50))
assert {:error, _, _} = Chain.last_db_block_status()
end
end
describe "last_cache_block_status/0" do
test "returns success if cache is not stale" do
insert(:block, consensus: true)
assert {:ok, _, _} = Chain.last_cache_block_status()
end
test "return error if cache is stale" do
insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50))
assert {:error, _, _} = Chain.last_cache_block_status()
end
end
describe "ERC721_token_instance_from_token_id_and_token_address/2" do
test "return ERC721 token instance" do
contract_address = insert(:address)
token_id = 10
insert(:token_transfer,
from_address: contract_address,
token_contract_address: contract_address,
token_id: token_id
)
assert {:ok, result} =
Chain.erc721_token_instance_from_token_id_and_token_address(token_id, contract_address.hash)
assert result.token_id == Decimal.new(token_id)
end
end
describe "upsert_token_instance/1" do
test "insert a new token instance with valid params" do
token = insert(:token)
params = %{
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
metadata: %{uri: "http://example.com"}
}
{:ok, result} = Chain.upsert_token_instance(params)
assert result.token_id == Decimal.new(1)
assert result.metadata == params.metadata
assert result.token_contract_address_hash == token.contract_address_hash
end
test "replaces existing token instance record" do
token = insert(:token)
params = %{
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
metadata: %{uri: "http://example.com"}
}
{:ok, _} = Chain.upsert_token_instance(params)
params1 = %{
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
metadata: %{uri: "http://example1.com"}
}
{:ok, result} = Chain.upsert_token_instance(params1)
assert result.token_id == Decimal.new(1)
assert result.metadata == params1.metadata
assert result.token_contract_address_hash == token.contract_address_hash
end
test "fails to import with invalid params" do
params = %{
token_id: 1,
metadata: %{uri: "http://example.com"}
}
{:error,
%{
errors: [
token_contract_address_hash: {"can't be blank", [validation: :required]}
],
valid?: false
}} = Chain.upsert_token_instance(params)
end
test "inserts just an error without metadata" do
token = insert(:token)
error = "no uri"
params = %{
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
error: error
}
{:ok, result} = Chain.upsert_token_instance(params)
assert result.error == error
end
test "nillifies error" do
token = insert(:token)
insert(:token_instance,
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
error: "no uri"
)
params = %{
token_id: 1,
token_contract_address_hash: token.contract_address_hash,
metadata: %{uri: "http://example1.com"}
}
{:ok, result} = Chain.upsert_token_instance(params)
assert is_nil(result.error)
assert result.metadata == params.metadata
end
end
describe "address_to_logs/2" do
test "fetches logs" do
%Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log,
block: transaction1.block,
block_number: transaction1.block_number,
transaction: transaction1,
index: 1,
address: address
)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log,
block: transaction2.block,
block_number: transaction2.block_number,
transaction: transaction2,
index: 2,
address: address
)
assert Enum.count(Chain.address_to_logs(address_hash)) == 2
end
test "paginates logs" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number)
2..51
|> Enum.map(fn index ->
insert(:log,
block: transaction.block,
transaction: transaction,
index: index,
address: address,
block_number: transaction.block_number
)
end)
|> Enum.map(& &1.index)
paging_options1 = %PagingOptions{page_size: 1}
[_log] = Chain.address_to_logs(address_hash, paging_options: paging_options1)
paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}}
assert Enum.count(Chain.address_to_logs(address_hash, paging_options: paging_options2)) == 50
end
test "searches logs by topic when the first topic matches" do
%Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log,
block: transaction1.block,
transaction: transaction1,
index: 1,
address: address,
block_number: transaction1.block_number
)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log,
block: transaction2.block,
transaction: transaction2,
index: 2,
address: address,
first_topic: "test",
block_number: transaction2.block_number
)
[found_log] = Chain.address_to_logs(address_hash, topic: "test")
assert found_log.transaction.hash == transaction2.hash
end
test "searches logs by topic when the fourth topic matches" do
%Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log,
block: transaction1.block,
block_number: transaction1.block_number,
transaction: transaction1,
index: 1,
address: address,
fourth_topic: "test"
)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log,
block: transaction2.block,
block_number: transaction2.block.number,
transaction: transaction2,
index: 2,
address: address
)
[found_log] = Chain.address_to_logs(address_hash, topic: "test")
assert found_log.transaction.hash == transaction1.hash
end
end
describe "address_to_mined_transactions_with_rewards/2" do
test "without transactions" do
%Address{hash: address_hash} = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert [] == Chain.address_to_mined_transactions_with_rewards(address_hash)
end
test "with from transactions" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
assert [transaction] ==
Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to transactions" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
assert [transaction] ==
Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :from" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
# only contains "from" transaction
assert [transaction] ==
Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :to" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
assert [transaction] ==
Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and no :direction option" do
%Address{hash: address_hash} = address = insert(:address)
block = insert(:block)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block(block)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block(block)
assert [transaction2, transaction1] ==
Chain.address_to_mined_transactions_with_rewards(address_hash)
|> Repo.preload([:block, :to_address, :from_address])
end
test "does not include non-contract-creation parent transactions" do
transaction =
%Transaction{} =
:transaction
|> insert()
|> with_block()
%InternalTransaction{created_contract_address: address} =
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert [] == Chain.address_to_mined_transactions_with_rewards(address.hash)
end
test "returns transactions that have token transfers for the given to_address" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address, to_address_hash: address.hash)
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction
)
assert [transaction.hash] ==
Chain.address_to_mined_transactions_with_rewards(address_hash)
|> Enum.map(& &1.hash)
end
test "with transactions can be paginated" do
%Address{hash: address_hash} = address = insert(:address)
second_page_hashes =
2
|> insert_list(:transaction, from_address: address)
|> with_block()
|> Enum.map(& &1.hash)
%Transaction{block_number: block_number, index: index} =
:transaction
|> insert(from_address: address)
|> with_block()
assert second_page_hashes ==
address_hash
|> Chain.address_to_mined_transactions_with_rewards(
paging_options: %PagingOptions{
key: {block_number, index},
page_size: 2
}
)
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
test "returns results in reverse chronological order by block number and transaction index" do
%Address{hash: address_hash} = address = insert(:address)
a_block = insert(:block, number: 6000)
%Transaction{hash: first} =
:transaction
|> insert(to_address: address)
|> with_block(a_block)
%Transaction{hash: second} =
:transaction
|> insert(to_address: address)
|> with_block(a_block)
%Transaction{hash: third} =
:transaction
|> insert(to_address: address)
|> with_block(a_block)
%Transaction{hash: fourth} =
:transaction
|> insert(to_address: address)
|> with_block(a_block)
b_block = insert(:block, number: 2000)
%Transaction{hash: fifth} =
:transaction
|> insert(to_address: address)
|> with_block(b_block)
%Transaction{hash: sixth} =
:transaction
|> insert(to_address: address)
|> with_block(b_block)
result =
address_hash
|> Chain.address_to_mined_transactions_with_rewards()
|> Enum.map(& &1.hash)
assert [fourth, third, second, first, sixth, fifth] == result
end
test "with emission rewards" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: "0x0000000000000000000000000000000000000005",
keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
)
block = insert(:block)
block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :validator
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :emission_funds
)
# isValidator => true
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
end
)
# getPayoutByMining => 0x0000000000000000000000000000000000000001
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
end
)
res = Chain.address_to_mined_transactions_with_rewards(block.miner.hash)
assert [{_, _}] = res
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: nil,
keys_manager_contract_address: nil
)
end)
end
test "with emission rewards and transactions" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: "0x0000000000000000000000000000000000000005",
keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
)
block = insert(:block)
block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :validator
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :emission_funds
)
:transaction
|> insert(to_address: block.miner)
|> with_block(block)
|> Repo.preload(:token_transfers)
# isValidator => true
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
end
)
# getPayoutByMining => 0x0000000000000000000000000000000000000001
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
end
)
assert [_, {_, _}] = Chain.address_to_mined_transactions_with_rewards(block.miner.hash, direction: :to)
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: nil,
keys_manager_contract_address: nil
)
end)
end
test "with transactions if rewards are not in the range of blocks" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
block = insert(:block)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :validator
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :emission_funds
)
:transaction
|> insert(from_address: block.miner)
|> with_block()
|> Repo.preload(:token_transfers)
assert [_] = Chain.address_to_mined_transactions_with_rewards(block.miner.hash, direction: :from)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
end
test "with emissions rewards, but feature disabled" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
block = insert(:block)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :validator
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :emission_funds
)
assert [] == Chain.address_to_mined_transactions_with_rewards(block.miner.hash)
end
end
describe "address_to_transactions_tasks_range_of_blocks/2" do
test "returns empty extremums if no transactions" do
address = insert(:address)
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => nil,
:max_block_number => 0
}
end
test "returns correct extremums for from_address" do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for to_address" do
address = insert(:address)
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for created_contract_address" do
address = insert(:address)
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for multiple number of transactions" do
address = insert(:address)
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1000))
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 999))
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1003))
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1001))
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1004))
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 1002))
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 998))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 998,
:max_block_number => 1004
}
end
end
describe "total_transactions_sent_by_address/1" do
test "increments +1 in the last nonce result" do
address = insert(:address)
:transaction
|> insert(nonce: 100, from_address: address)
|> with_block(insert(:block, number: 1000))
assert Chain.total_transactions_sent_by_address(address.hash) == 101
end
test "returns 0 when the address did not send transactions" do
address = insert(:address)
:transaction
|> insert(nonce: 100, to_address: address)
|> with_block(insert(:block, number: 1000))
assert Chain.total_transactions_sent_by_address(address.hash) == 0
end
end
describe "balance/2" do
test "with Address.t with :wei" do
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :wei) == nil
end
test "with Address.t with :gwei" do
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :gwei) == nil
end
test "with Address.t with :ether" do
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :ether) == nil
end
end
describe "block_to_transactions/2" do
test "without transactions" do
block = insert(:block)
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert [] = Chain.block_to_transactions(block.hash)
end
test "with transactions" do
%Transaction{block: block, hash: transaction_hash} =
:transaction
|> insert()
|> with_block()
assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block.hash)
end
test "with transactions can be paginated by {index}" do
block = insert(:block)
second_page_hashes =
50
|> insert_list(:transaction)
|> with_block(block)
|> Enum.map(& &1.hash)
%Transaction{index: index} =
:transaction
|> insert()
|> with_block(block)
assert second_page_hashes ==
block.hash
|> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50})
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
block = insert(:block)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction =
:transaction
|> insert()
|> with_block(block)
insert_list(
2,
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block.hash))
assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2
end
end
describe "block_to_transaction_count/1" do
test "without transactions" do
block = insert(:block)
assert Chain.block_to_transaction_count(block.hash) == 0
end
test "with transactions" do
%Transaction{block: block} =
:transaction
|> insert()
|> with_block()
assert Chain.block_to_transaction_count(block.hash) == 1
end
end
describe "address_to_incoming_transaction_count/1" do
test "without transactions" do
%Address{hash: address_hash} = insert(:address)
assert Chain.address_to_incoming_transaction_count(address_hash) == 0
end
test "with transactions" do
%Transaction{to_address: to_address} = insert(:transaction)
assert Chain.address_to_incoming_transaction_count(to_address.hash) == 1
end
end
describe "confirmations/1" do
test "with block.number == block_height " do
block = insert(:block)
block_height = Chain.block_height()
assert block.number == block_height
assert {:ok, 0} = Chain.confirmations(block, block_height: block_height)
end
test "with block.number < block_height" do
block = insert(:block)
block_height = block.number + 2
assert block.number < block_height
assert {:ok, confirmations} = Chain.confirmations(block, block_height: block_height)
assert confirmations == block_height - block.number
end
end
describe "fee/2" do
test "without receipt with :wei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :wei) ==
{:maximum, Decimal.new(6)}
end
test "without receipt with :gwei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :gwei) ==
{:maximum, Decimal.new("6e-9")}
end
test "without receipt with :ether unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :ether) ==
{:maximum, Decimal.new("6e-18")}
end
test "with receipt with :wei unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
gas_used: Decimal.new(2)
},
:wei
) == {:actual, Decimal.new(4)}
end
test "with receipt with :gwei unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
gas_used: Decimal.new(2)
},
:gwei
) == {:actual, Decimal.new("4e-9")}
end
test "with receipt with :ether unit" do
assert Chain.fee(
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
gas_used: Decimal.new(2)
},
:ether
) == {:actual, Decimal.new("4e-18")}
end
end
describe "fetch_token_transfers_from_token_hash/2" do
test "without token transfers" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
assert Chain.fetch_token_transfers_from_token_hash(contract_address_hash) == []
end
test "with token transfers" do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
%TokenTransfer{
transaction_hash: token_transfer_transaction_hash,
log_index: token_transfer_log_index,
token_contract_address_hash: token_contract_address_hash
} = insert(:token_transfer, to_address: address, transaction: transaction)
assert token_contract_address_hash
|> Chain.fetch_token_transfers_from_token_hash()
|> Enum.map(&{&1.transaction_hash, &1.log_index}) == [
{token_transfer_transaction_hash, token_transfer_log_index}
]
end
end
describe "finished_indexing?/0" do
test "finished indexing" do
block = insert(:block, number: 1)
:transaction
|> insert()
|> with_block(block)
assert Chain.finished_indexing?()
end
test "finished indexing (no txs)" do
assert Chain.finished_indexing?()
end
test "not finished indexing" do
block = insert(:block, number: 1)
:transaction
|> insert()
|> with_block(block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
refute Chain.finished_indexing?()
end
end
describe "gas_price/2" do
test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end
test ":gwei unit" do
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end
test ":ether unit" do
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end
end
describe "hashes_to_addresses/1" do
test "with existing addresses" do
address1 = insert(:address, hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
address2 = insert(:address, hash: "0x6aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
# in opposite of insertion order, to check that ordering matches ordering of arguments
# regression test for https://github.com/poanetwork/blockscout/issues/843
hashes = [address2.hash, address1.hash]
[found_address1, found_address2] = Explorer.Chain.hashes_to_addresses(hashes)
%Explorer.Chain.Address{hash: found_hash1} = found_address1
%Explorer.Chain.Address{hash: found_hash2} = found_address2
assert found_hash1 == address2.hash
assert found_hash2 == address1.hash
hashes = [address1.hash, address2.hash]
[found_address1, found_address2] = Explorer.Chain.hashes_to_addresses(hashes)
%Explorer.Chain.Address{hash: found_hash1} = found_address1
%Explorer.Chain.Address{hash: found_hash2} = found_address2
assert found_hash1 == address1.hash
assert found_hash2 == address2.hash
end
test "with nonexistent addresses" do
hash1 = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"
hash2 = "0x6aaeb6053f3e94c9b9a09f33669435e7ef1beaed"
hashes = [hash1, hash2]
assert Explorer.Chain.hashes_to_addresses(hashes) == []
end
end
describe "hash_to_transaction/2" do
test "with transaction with block required without block returns {:error, :not_found}" do
%Transaction{hash: hash_with_block} =
:transaction
|> insert()
|> with_block()
%Transaction{hash: hash_without_index} = insert(:transaction)
assert {:ok, %Transaction{hash: ^hash_with_block}} =
Chain.hash_to_transaction(
hash_with_block,
necessity_by_association: %{block: :required}
)
assert {:error, :not_found} =
Chain.hash_to_transaction(
hash_without_index,
necessity_by_association: %{block: :required}
)
assert {:ok, %Transaction{hash: ^hash_without_index}} =
Chain.hash_to_transaction(
hash_without_index,
necessity_by_association: %{block: :optional}
)
end
test "transaction with multiple create internal transactions is returned" do
transaction =
%Transaction{hash: hash_with_block} =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
Enum.each(1..3, fn index ->
insert(:internal_transaction_create,
transaction: transaction,
index: index,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index
)
end)
assert {:ok, %Transaction{hash: ^hash_with_block}} = Chain.hash_to_transaction(hash_with_block)
end
end
describe "hash_to_address/1" do
test "returns not found if the address doesn't exist" do
hash_str = "0xcbbcd5ac86f9a50e13313633b262e16f695a90c2"
{:ok, hash} = Chain.string_to_address_hash(hash_str)
assert {:error, :not_found} = Chain.hash_to_address(hash)
end
test "returns the correct address if it exists" do
address = insert(:address)
assert {:ok, address} = Chain.hash_to_address(address.hash)
end
test "has_decompiled_code? is true if there are decompiled contracts" do
address = insert(:address)
insert(:decompiled_smart_contract, address_hash: address.hash)
{:ok, found_address} = Chain.hash_to_address(address.hash)
assert found_address.has_decompiled_code?
end
test "has_decompiled_code? is false if there are no decompiled contracts" do
address = insert(:address)
{:ok, found_address} = Chain.hash_to_address(address.hash)
refute found_address.has_decompiled_code?
end
end
describe "token_contract_address_from_token_name/1" do
test "return not found if token doesn't exist" do
name = "AYR"
assert {:error, :not_found} = Chain.token_contract_address_from_token_name(name)
end
test "return the correct token if it exists" do
name = "AYR"
insert(:token, symbol: name)
assert {:ok, _} = Chain.token_contract_address_from_token_name(name)
end
test "return only one result if multiple records are found" do
name = "TOKEN"
insert(:token, symbol: name)
insert(:token, symbol: name)
assert {:ok, _} = Chain.token_contract_address_from_token_name(name)
end
end
describe "find_or_insert_address_from_hash/1" do
test "returns an address if it already exists" do
address = insert(:address)
assert {:ok, address} = Chain.find_or_insert_address_from_hash(address.hash)
end
test "returns an address if it doesn't exist" do
hash_str = "0xcbbcd5ac86f9a50e13313633b262e16f695a90c2"
{:ok, hash} = Chain.string_to_address_hash(hash_str)
assert {:ok, %Chain.Address{hash: hash}} = Chain.find_or_insert_address_from_hash(hash)
end
end
describe "hashes_to_transactions/2" do
test "with transaction with block required without block returns nil" do
[%Transaction{hash: hash_with_block1}, %Transaction{hash: hash_with_block2}] =
2
|> insert_list(:transaction)
|> with_block()
[%Transaction{hash: hash_without_index1}, %Transaction{hash: hash_without_index2}] = insert_list(2, :transaction)
assert [%Transaction{hash: ^hash_with_block2}, %Transaction{hash: ^hash_with_block1}] =
Chain.hashes_to_transactions(
[hash_with_block1, hash_with_block2],
necessity_by_association: %{block: :required}
)
assert [] =
Chain.hashes_to_transactions(
[hash_without_index1, hash_without_index2],
necessity_by_association: %{block: :required}
)
assert [hash_without_index1, hash_without_index2]
|> Chain.hashes_to_transactions(necessity_by_association: %{block: :optional})
|> Enum.all?(&(&1.hash in [hash_without_index1, hash_without_index2]))
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
[transaction1, transaction2] =
2
|> insert_list(:transaction)
|> with_block()
%TokenTransfer{transaction_hash: transaction_hash1, log_index: log_index1} =
insert(
:token_transfer,
to_address: address,
transaction: transaction1,
token_contract_address: token_contract_address,
token: token
)
%TokenTransfer{transaction_hash: transaction_hash2, log_index: log_index2} =
insert(
:token_transfer,
to_address: address,
transaction: transaction2,
token_contract_address: token_contract_address,
token: token
)
fetched_transactions = Explorer.Chain.hashes_to_transactions([transaction1.hash, transaction2.hash])
assert Enum.all?(fetched_transactions, fn transaction ->
%TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} =
hd(transaction.token_transfers)
{transaction_hash, log_index} in [{transaction_hash1, log_index1}, {transaction_hash2, log_index2}]
end)
end
end
describe "indexed_ratio/0" do
test "returns indexed ratio" do
for index <- 5..9 do
insert(:block, number: index)
end
assert Decimal.cmp(Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq
end
test "returns 0 if no blocks" do
assert Decimal.new(0) == Chain.indexed_ratio()
end
test "returns 1.0 if fully indexed blocks" do
for index <- 0..9 do
insert(:block, number: index)
Process.sleep(200)
end
assert Decimal.cmp(Chain.indexed_ratio(), 1) == :eq
end
end
describe "fetch_min_block_number/0" do
test "fetches min block numbers" do
for index <- 5..9 do
insert(:block, number: index)
Process.sleep(200)
end
assert 5 = Chain.fetch_min_block_number()
end
test "fetches min when there are no blocks" do
assert 0 = Chain.fetch_min_block_number()
end
end
describe "fetch_max_block_number/0" do
test "fetches max block numbers" do
for index <- 5..9 do
insert(:block, number: index)
end
assert 9 = Chain.fetch_max_block_number()
end
test "fetches max when there are no blocks" do
assert 0 = Chain.fetch_max_block_number()
end
end
describe "fetch_sum_coin_total_supply/0" do
test "fetches coin total supply" do
for index <- 0..4 do
insert(:address, fetched_coin_balance: index)
end
assert "10" = Decimal.to_string(Chain.fetch_sum_coin_total_supply())
end
test "fetches coin total supply when there are no blocks" do
assert 0 = Chain.fetch_sum_coin_total_supply()
end
end
describe "address_hash_to_token_transfers/2" do
test "returns just the token transfers related to the given contract address" do
contract_address =
insert(
:address,
contract_code: Factory.data("contract_code")
)
transaction =
:transaction
|> insert(to_address: contract_address)
|> with_block()
token_transfer =
insert(
:token_transfer,
to_address: contract_address,
transaction: transaction
)
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction
)
transaction =
contract_address.hash
|> Chain.address_hash_to_token_transfers()
|> List.first()
token_transfers_contract_address =
Enum.map(
transaction.token_transfers,
&{&1.transaction_hash, &1.log_index}
)
assert token_transfers_contract_address == [
{token_transfer.transaction_hash, token_transfer.log_index}
]
end
test "returns just the token transfers related to the given address" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
token_transfer =
insert(
:token_transfer,
to_address: address,
transaction: transaction
)
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction
)
transaction =
address_hash
|> Chain.address_hash_to_token_transfers()
|> List.first()
token_transfers_related =
Enum.map(
transaction.token_transfers,
&{&1.transaction_hash, &1.log_index}
)
assert token_transfers_related == [
{token_transfer.transaction_hash, token_transfer.log_index}
]
end
test "fetches token transfers by address hash" do
address = insert(:address)
token_transfer =
insert(
:token_transfer,
from_address: address,
amount: 1
)
[transaction_hash] =
address.hash
|> Chain.address_hash_to_token_transfers()
|> Enum.map(& &1.hash)
assert transaction_hash == token_transfer.transaction_hash
end
end
# Full tests in `test/explorer/import_test.exs`
describe "import/1" do
@import_data %{
blocks: %{
params: [
%{
consensus: true,
difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454,
gas_limit: 6_946_336,
gas_used: 50450,
hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
nonce: 0,
number: 37,
parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e",
size: 719,
timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"),
total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509
}
]
},
block_second_degree_relations: %{
params: [
%{
nephew_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
uncle_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471be",
index: 0
}
]
},
broadcast: true,
internal_transactions: %{
params: [
%{
block_number: 37,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
trace_address: [],
type: "call",
call_type: "call",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
gas: 4_677_320,
gas_used: 27770,
input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
output: "0x",
value: 0
}
],
with: :blockless_changeset
},
logs: %{
params: [
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
fourth_topic: nil,
index: 0,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "mined"
}
]
},
transactions: %{
params: [
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
block_number: 37,
cumulative_gas_used: 50450,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
gas: 4_700_000,
gas_price: 100_000_000_000,
gas_used: 50450,
hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
nonce: 4,
public_key:
"0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9",
r: 0xA7F8F45CCE375BB7AF8750416E1B03E0473F93C256DA2285D1134FC97A700E01,
s: 0x1F87A076F13824F4BE8963E3DFFD7300DAE64D5F23C9A062AF0C6EAD347C135F,
standard_v: 1,
status: :ok,
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
v: 0xBE,
value: 0
}
]
},
addresses: %{
params: [
%{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"},
%{hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"},
%{hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"}
]
},
tokens: %{
on_conflict: :nothing,
params: [
%{
contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
type: "ERC-20"
}
]
},
token_transfers: %{
params: [
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
amount: Decimal.new(1_000_000_000_000_000_000),
block_number: 37,
log_index: 0,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
]
}
}
test "with valid data" do
difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454)
total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509)
token_transfer_amount = Decimal.new(1_000_000_000_000_000_000)
gas_limit = Decimal.new(6_946_336)
gas_used = Decimal.new(50450)
assert {:ok,
%{
addresses: [
%Address{
hash: %Hash{
byte_count: 20,
bytes:
<<81, 92, 9, 197, 187, 161, 237, 86, 107, 2, 165, 176, 89, 158, 197, 213, 208, 174, 231, 61>>
},
inserted_at: %{},
updated_at: %{}
},
%Address{
hash: %Hash{
byte_count: 20,
bytes:
<<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65,
91>>
},
inserted_at: %{},
updated_at: %{}
},
%Address{
hash: %Hash{
byte_count: 20,
bytes:
<<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122,
202>>
},
inserted_at: %{},
updated_at: %{}
}
],
blocks: [
%Block{
consensus: true,
difficulty: ^difficulty,
gas_limit: ^gas_limit,
gas_used: ^gas_used,
hash: %Hash{
byte_count: 32,
bytes:
<<246, 180, 184, 200, 141, 243, 235, 210, 82, 236, 71, 99, 40, 51, 77, 192, 38, 207, 102, 96,
106, 132, 251, 118, 155, 61, 60, 188, 204, 132, 113, 189>>
},
miner_hash: %Hash{
byte_count: 20,
bytes:
<<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122,
202>>
},
nonce: %Explorer.Chain.Hash{
byte_count: 8,
bytes: <<0, 0, 0, 0, 0, 0, 0, 0>>
},
number: 37,
parent_hash: %Hash{
byte_count: 32,
bytes:
<<195, 123, 186, 215, 5, 121, 69, 209, 191, 18, 140, 31, 240, 9, 251, 26, 214, 50, 17, 11, 246,
160, 0, 170, 192, 37, 168, 15, 119, 102, 182, 110>>
},
size: 719,
timestamp: %DateTime{
year: 2017,
month: 12,
day: 15,
hour: 21,
minute: 6,
second: 30,
microsecond: {0, 6},
std_offset: 0,
utc_offset: 0,
time_zone: "Etc/UTC",
zone_abbr: "UTC"
},
total_difficulty: ^total_difficulty,
inserted_at: %{},
updated_at: %{}
}
],
internal_transactions: [],
logs: [
%Log{
address_hash: %Hash{
byte_count: 20,
bytes:
<<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65,
91>>
},
data: %Data{
bytes:
<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179,
167, 100, 0, 0>>
},
index: 0,
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
fourth_topic: nil,
transaction_hash: %Hash{
byte_count: 32,
bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
},
type: "mined",
inserted_at: %{},
updated_at: %{}
}
],
transactions: [
%Transaction{
block_number: 37,
index: 0,
hash: %Hash{
byte_count: 32,
bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}
}
],
tokens: [
%Token{
contract_address_hash: %Hash{
byte_count: 20,
bytes:
<<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65,
91>>
},
type: "ERC-20",
inserted_at: %{},
updated_at: %{}
}
],
token_transfers: [
%TokenTransfer{
amount: ^token_transfer_amount,
log_index: 0,
from_address_hash: %Hash{
byte_count: 20,
bytes:
<<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122,
202>>
},
to_address_hash: %Hash{
byte_count: 20,
bytes:
<<81, 92, 9, 197, 187, 161, 237, 86, 107, 2, 165, 176, 89, 158, 197, 213, 208, 174, 231, 61>>
},
token_contract_address_hash: %Hash{
byte_count: 20,
bytes:
<<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65,
91>>
},
transaction_hash: %Hash{
byte_count: 32,
bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
},
inserted_at: %{},
updated_at: %{}
}
]
}} = Chain.import(@import_data)
end
end
describe "list_blocks/2" do
test "without blocks" do
assert [] = Chain.list_blocks()
end
test "with blocks" do
%Block{hash: hash} = insert(:block)
assert [%Block{hash: ^hash}] = Chain.list_blocks()
end
test "with blocks can be paginated" do
second_page_block_ids =
50
|> insert_list(:block)
|> Enum.map(& &1.number)
block = insert(:block)
assert second_page_block_ids ==
[paging_options: %PagingOptions{key: {block.number}, page_size: 50}]
|> Chain.list_blocks()
|> Enum.map(& &1.number)
|> Enum.reverse()
end
end
describe "block_hash_by_number/1" do
test "without blocks returns empty map" do
assert Chain.block_hash_by_number([]) == %{}
end
test "with consensus block returns mapping" do
block = insert(:block)
assert Chain.block_hash_by_number([block.number]) == %{block.number => block.hash}
end
test "with non-consensus block does not return mapping" do
block = insert(:block, consensus: false)
assert Chain.block_hash_by_number([block.number]) == %{}
end
end
describe "list_top_addresses/0" do
test "without addresses with balance > 0" do
insert(:address, fetched_coin_balance: 0)
assert [] = Chain.list_top_addresses()
end
test "with top addresses in order" do
address_hashes =
4..1
|> Enum.map(&insert(:address, fetched_coin_balance: &1))
|> Enum.map(& &1.hash)
assert address_hashes ==
Chain.list_top_addresses()
|> Enum.map(fn {address, _transaction_count} -> address end)
|> Enum.map(& &1.hash)
end
# flaky test
# test "with top addresses in order with matching value" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# tail =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# first_result_hash =
# :address
# |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4))
# |> Map.fetch!(:hash)
# assert [first_result_hash | tail] ==
# Chain.list_top_addresses()
# |> Enum.map(fn {address, _transaction_count} -> address end)
# |> Enum.map(& &1.hash)
# end
# flaky test
# test "paginates addresses" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# result =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# options = [paging_options: %PagingOptions{page_size: 1}]
# [{top_address, _}] = Chain.list_top_addresses(options)
# assert top_address.hash == List.first(result)
# tail_options = [
# paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3}
# ]
# tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end)
# [_ | expected_tail] = result
# assert tail_result == expected_tail
# end
end
describe "stream_blocks_without_rewards/2" do
test "includes consensus blocks" do
%Block{hash: consensus_hash} = insert(:block, consensus: true)
assert {:ok, [%Block{hash: ^consensus_hash}]} = Chain.stream_blocks_without_rewards([], &[&1 | &2])
end
test "does not include consensus block that has a reward" do
%Block{hash: consensus_hash, miner_hash: miner_hash} = insert(:block, consensus: true)
insert(:reward, address_hash: miner_hash, block_hash: consensus_hash)
assert {:ok, []} = Chain.stream_blocks_without_rewards([], &[&1 | &2])
end
# https://github.com/poanetwork/blockscout/issues/1310 regression test
test "does not include non-consensus blocks" do
insert(:block, consensus: false)
assert {:ok, []} = Chain.stream_blocks_without_rewards([], &[&1 | &2])
end
end
describe "get_blocks_validated_by_address/2" do
test "returns nothing when there are no blocks" do
%Address{hash: address_hash} = insert(:address)
assert [] = Chain.get_blocks_validated_by_address(address_hash)
end
test "returns the blocks validated by a specified address" do
%Address{hash: address_hash} = address = insert(:address)
another_address = insert(:address)
block = insert(:block, miner: address, miner_hash: address.hash)
insert(:block, miner: another_address, miner_hash: another_address.hash)
results =
address_hash
|> Chain.get_blocks_validated_by_address()
|> Enum.map(& &1.hash)
assert results == [block.hash]
end
test "with blocks can be paginated" do
%Address{hash: address_hash} = address = insert(:address)
first_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 0)
second_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 2)
assert [first_page_block.number] ==
[paging_options: %PagingOptions{key: {1}, page_size: 1}]
|> Chain.get_blocks_validated_by_address(address_hash)
|> Enum.map(& &1.number)
|> Enum.reverse()
assert [second_page_block.number] ==
[paging_options: %PagingOptions{key: {3}, page_size: 1}]
|> Chain.get_blocks_validated_by_address(address_hash)
|> Enum.map(& &1.number)
|> Enum.reverse()
end
end
describe "each_address_block_validation_count/0" do
test "streams block validation count grouped by the address that validated them (`address_hash`)" do
address = insert(:address)
insert(:block, miner: address, miner_hash: address.hash)
{:ok, agent_pid} = Agent.start_link(fn -> [] end)
Chain.each_address_block_validation_count(fn entry -> Agent.update(agent_pid, &[entry | &1]) end)
results = Agent.get(agent_pid, &Enum.reverse/1)
assert length(results) == 1
assert results == [{address.hash, 1}]
end
end
describe "number_to_block/1" do
test "without block" do
assert {:error, :not_found} = Chain.number_to_block(-1)
end
test "with block" do
%Block{number: number} = insert(:block)
assert {:ok, %Block{number: ^number}} = Chain.number_to_block(number)
end
end
describe "address_to_internal_transactions/1" do
test "with single transaction containing two internal transactions" do
address = insert(:address)
block = insert(:block, number: 2000)
transaction =
:transaction
|> insert()
|> with_block(block)
%InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} =
insert(:internal_transaction,
index: 1,
transaction: transaction,
to_address: address,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
index: 2,
transaction: transaction,
to_address: address,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index
)
result =
address.hash
|> Chain.address_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
assert Enum.member?(result, {first_transaction_hash, first_index})
assert Enum.member?(result, {second_transaction_hash, second_index})
end
test "loads associations in necessity_by_association" do
%Address{hash: address_hash} = address = insert(:address)
block = insert(:block, number: 2000)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{}
}
| _
] = Chain.address_to_internal_transactions(address_hash)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: %Address{},
transaction: %Transaction{}
}
| _
] =
Chain.address_to_internal_transactions(
address_hash,
necessity_by_association: %{
[from_address: :names] => :optional,
[to_address: :names] => :optional,
:transaction => :optional
}
)
end
test "returns results in reverse chronological order by block number, transaction index, internal transaction index" do
address = insert(:address)
block = insert(:block, number: 7000)
pending_transaction =
:transaction
|> insert()
|> with_block(block)
%InternalTransaction{transaction_hash: first_pending_transaction_hash, index: first_pending_index} =
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
index: 1,
block_number: pending_transaction.block_number,
block_hash: pending_transaction.block_hash,
block_index: 1,
transaction_index: pending_transaction.index
)
%InternalTransaction{transaction_hash: second_pending_transaction_hash, index: second_pending_index} =
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
index: 2,
block_number: pending_transaction.block_number,
block_hash: pending_transaction.block_hash,
block_index: 2,
transaction_index: pending_transaction.index
)
a_block = insert(:block, number: 2000)
first_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} =
insert(
:internal_transaction,
transaction: first_a_transaction,
to_address: address,
index: 1,
block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 1,
transaction_index: first_a_transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(
:internal_transaction,
transaction: first_a_transaction,
to_address: address,
index: 2,
block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 2,
transaction_index: first_a_transaction.index
)
second_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} =
insert(
:internal_transaction,
transaction: second_a_transaction,
to_address: address,
index: 1,
block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 4,
transaction_index: second_a_transaction.index
)
%InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} =
insert(
:internal_transaction,
transaction: second_a_transaction,
to_address: address,
index: 2,
block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 5,
transaction_index: second_a_transaction.index
)
b_block = insert(:block, number: 6000)
first_b_transaction =
:transaction
|> insert()
|> with_block(b_block)
%InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} =
insert(
:internal_transaction,
transaction: first_b_transaction,
to_address: address,
index: 1,
block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 1,
transaction_index: first_b_transaction.index
)
%InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} =
insert(
:internal_transaction,
transaction: first_b_transaction,
to_address: address,
index: 2,
block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 2,
transaction_index: first_b_transaction.index
)
result =
address.hash
|> Chain.address_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [
{second_pending_transaction_hash, second_pending_index},
{first_pending_transaction_hash, first_pending_index},
{sixth_transaction_hash, sixth_index},
{fifth_transaction_hash, fifth_index},
{fourth_transaction_hash, fourth_index},
{third_transaction_hash, third_index},
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] == result
end
test "pages by {block_number, transaction_index, index}" do
address = insert(:address)
pending_transaction = insert(:transaction)
old_block = insert(:block, consensus: false)
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
block_hash: old_block.hash,
block_index: 1,
index: 1
)
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
block_hash: old_block.hash,
block_index: 2,
index: 2
)
a_block = insert(:block, number: 2000)
first_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} =
insert(
:internal_transaction,
transaction: first_a_transaction,
to_address: address,
index: 1,
block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 1,
transaction_index: first_a_transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(
:internal_transaction,
transaction: first_a_transaction,
to_address: address,
index: 2,
block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 2,
transaction_index: first_a_transaction.index
)
second_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} =
insert(
:internal_transaction,
transaction: second_a_transaction,
to_address: address,
index: 1,
block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 4,
transaction_index: second_a_transaction.index
)
%InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} =
insert(
:internal_transaction,
transaction: second_a_transaction,
to_address: address,
index: 2,
block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 5,
transaction_index: second_a_transaction.index
)
b_block = insert(:block, number: 6000)
first_b_transaction =
:transaction
|> insert()
|> with_block(b_block)
%InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} =
insert(
:internal_transaction,
transaction: first_b_transaction,
to_address: address,
index: 1,
block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 1,
transaction_index: first_b_transaction.index
)
%InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} =
insert(
:internal_transaction,
transaction: first_b_transaction,
to_address: address,
index: 2,
block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 2,
transaction_index: first_b_transaction.index
)
# When paged, internal transactions need an associated block number, so `second_pending` and `first_pending` are
# excluded.
assert [
{sixth_transaction_hash, sixth_index},
{fifth_transaction_hash, fifth_index},
{fourth_transaction_hash, fourth_index},
{third_transaction_hash, third_index},
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6001, 3, 2}, page_size: 8}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
# block number ==, transaction index ==, internal transaction index <
assert [
{fourth_transaction_hash, fourth_index},
{third_transaction_hash, third_index},
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6000, 0, 1}, page_size: 8}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
# block number ==, transaction index <
assert [
{fourth_transaction_hash, fourth_index},
{third_transaction_hash, third_index},
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6000, -1, -1}, page_size: 8}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
# block number <
assert [] ==
address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {2000, -1, -1}, page_size: 8}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
end
test "excludes internal transactions of type `call` when they are alone in the parent transaction" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:internal_transaction,
index: 0,
to_address: address,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert Enum.empty?(Chain.address_to_internal_transactions(address_hash))
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
%Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
expected =
insert(
:internal_transaction_create,
index: 0,
from_address: address,
transaction: transaction,
block_hash: transaction.block_hash,
block_index: 0,
block_number: transaction.block_number,
transaction_index: transaction.index
)
actual = Enum.at(Chain.address_to_internal_transactions(address_hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
end
describe "pending_transactions/0" do
test "without transactions" do
assert [] = Chain.recent_pending_transactions()
end
test "with transactions" do
%Transaction{hash: hash} = insert(:transaction)
assert [%Transaction{hash: ^hash}] = Chain.recent_pending_transactions()
end
test "with transactions can be paginated" do
second_page_hashes =
50
|> insert_list(:transaction)
|> Enum.map(& &1.hash)
%Transaction{inserted_at: inserted_at, hash: hash} = insert(:transaction)
assert second_page_hashes ==
[paging_options: %PagingOptions{key: {inserted_at, hash}, page_size: 50}]
|> Chain.recent_pending_transactions()
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
end
describe "transaction_estimated_count/1" do
test "returns integer" do
assert is_integer(Chain.transaction_estimated_count())
end
end
describe "transaction_to_internal_transactions/1" do
test "with transaction without internal transactions" do
transaction = insert(:transaction)
assert [] = Chain.transaction_to_internal_transactions(transaction.hash)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash excluding parent trace" do
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
first =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
second =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_hash: transaction.block_hash,
block_index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction.hash)
# excluding of internal transactions with type=call and index=0
assert 1 == length(results)
assert Enum.all?(
results,
&({&1.transaction_hash, &1.index} in [
{first.transaction_hash, first.index},
{second.transaction_hash, second.index}
])
)
assert internal_transaction.transaction.block_number == block.number
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
}
] = Chain.transaction_to_internal_transactions(transaction.hash)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: nil,
transaction: %Transaction{block: %Block{}}
}
] =
Chain.transaction_to_internal_transactions(
transaction.hash,
necessity_by_association: %{
:from_address => :optional,
:to_address => :optional,
[transaction: :block] => :optional
}
)
end
test "excludes internal transaction of type call with no siblings in the transaction" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
result = Chain.transaction_to_internal_transactions(transaction.hash)
assert Enum.empty?(result)
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction_create,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
type: :reward,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `selfdestruct` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
gas: nil,
type: :selfdestruct,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "returns the internal transactions in ascending index order" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: _, index: _} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
result =
transaction.hash
|> Chain.transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
# excluding of internal transactions with type=call and index=0
assert [{second_transaction_hash, second_index}] == result
end
test "pages by index" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: _, index: _} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} =
insert(:internal_transaction,
transaction: transaction,
index: 2,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index
)
assert [{second_transaction_hash, second_index}, {third_transaction_hash, third_index}] ==
transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{second_transaction_hash, second_index}] ==
transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{third_transaction_hash, third_index}] ==
transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
end
end
describe "all_transaction_to_internal_transactions/1" do
test "with transaction without internal transactions" do
transaction = insert(:transaction)
assert [] = Chain.all_transaction_to_internal_transactions(transaction.hash)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
first =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
second =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_hash: transaction.block_hash,
block_index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
results = [internal_transaction | _] = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert 2 == length(results)
assert Enum.all?(
results,
&({&1.transaction_hash, &1.index} in [
{first.transaction_hash, first.index},
{second.transaction_hash, second.index}
])
)
assert internal_transaction.transaction.block_number == block.number
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
}
] = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: nil,
transaction: %Transaction{block: %Block{}}
}
] =
Chain.all_transaction_to_internal_transactions(
transaction.hash,
necessity_by_association: %{
:from_address => :optional,
:to_address => :optional,
[transaction: :block] => :optional
}
)
end
test "not excludes internal transaction of type call with no siblings in the transaction" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
result = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert Enum.empty?(result) == false
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction_create,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
type: :reward,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `selfdestruct` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
gas: nil,
type: :selfdestruct,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "returns the internal transactions in ascending index order" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: transaction_hash, index: index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
result =
transaction.hash
|> Chain.all_transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{transaction_hash, index}, {second_transaction_hash, second_index}] == result
end
test "pages by index" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: transaction_hash, index: index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} =
insert(:internal_transaction,
transaction: transaction,
index: 2,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index
)
assert [{transaction_hash, index}, {second_transaction_hash, second_index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(
paging_options: %PagingOptions{key: {-1}, page_size: 2}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{transaction_hash, index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(
paging_options: %PagingOptions{key: {-1}, page_size: 1}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{third_transaction_hash, third_index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(paging_options: %PagingOptions{key: {1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
end
end
describe "transaction_to_logs/2" do
test "without logs" do
transaction = insert(:transaction)
assert [] = Chain.transaction_to_logs(transaction.hash)
end
test "with logs" do
transaction =
:transaction
|> insert()
|> with_block()
%Log{transaction_hash: transaction_hash, index: index} =
insert(:log, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash)
end
test "with logs can be paginated" do
transaction =
:transaction
|> insert()
|> with_block()
log =
insert(:log,
transaction: transaction,
index: 1,
block: transaction.block,
block_number: transaction.block_number
)
second_page_indexes =
2..51
|> Enum.map(fn index ->
insert(:log,
transaction: transaction,
index: index,
block: transaction.block,
block_number: transaction.block_number
)
end)
|> Enum.map(& &1.index)
assert second_page_indexes ==
transaction.hash
|> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50})
|> Enum.map(& &1.index)
end
test "with logs necessity_by_association loads associations" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:log, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs(
transaction.hash,
necessity_by_association: %{
address: :optional,
transaction: :optional
}
)
assert [
%Log{
address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_logs(transaction.hash)
end
end
describe "transaction_to_token_transfers/2" do
test "without token transfers" do
transaction = insert(:transaction)
assert [] = Chain.transaction_to_token_transfers(transaction.hash)
end
test "with token transfers" do
transaction =
:transaction
|> insert()
|> with_block()
%TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} =
insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] =
Chain.transaction_to_token_transfers(transaction.hash)
end
test "token transfers necessity_by_association loads associations" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] =
Chain.transaction_to_token_transfers(
transaction.hash,
necessity_by_association: %{
token: :optional,
transaction: :optional
}
)
assert [
%TokenTransfer{
token: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_token_transfers(transaction.hash)
end
end
describe "value/2" do
test "with InternalTransaction.t with :wei" do
assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end
test "with InternalTransaction.t with :gwei" do
assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end
test "with InternalTransaction.t with :ether" do
assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end
test "with Transaction.t with :wei" do
assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end
test "with Transaction.t with :gwei" do
assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.value(%Transaction{value: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end
test "with Transaction.t with :ether" do
assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.value(%Transaction{value: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end
end
describe "find_contract_address/1" do
test "doesn't find an address that doesn't have a code" do
address = insert(:address, contract_code: nil)
response = Chain.find_contract_address(address.hash)
assert {:error, :not_found} == response
end
test "doesn't find a nonexistent address" do
nonexistent_address_hash = Factory.address_hash()
response = Chain.find_contract_address(nonexistent_address_hash)
assert {:error, :not_found} == response
end
test "finds a contract address" do
address =
insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil, names: [])
|> Repo.preload([:contracts_creation_internal_transaction, :contracts_creation_transaction, :token])
options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
response = Chain.find_contract_address(address.hash, options, true)
assert response == {:ok, address}
end
end
describe "find_decompiled_contract_address/1" do
test "returns contract with decompiled contracts" do
address = insert(:address)
insert(:decompiled_smart_contract, address_hash: address.hash)
insert(:decompiled_smart_contract, address_hash: address.hash, decompiler_version: "2")
{:ok, address} = Chain.find_decompiled_contract_address(address.hash)
assert Enum.count(address.decompiled_smart_contracts) == 2
end
end
describe "block_reward/1" do
setup do
%{block_range: range} = emission_reward = insert(:emission_reward)
block = insert(:block, number: Enum.random(Range.new(range.from, range.to)))
insert(:transaction)
{:ok, block: block, emission_reward: emission_reward}
end
test "with block containing transactions", %{block: block, emission_reward: emission_reward} do
:transaction
|> insert(gas_price: 1)
|> with_block(block, gas_used: 1)
:transaction
|> insert(gas_price: 1)
|> with_block(block, gas_used: 2)
expected =
emission_reward.reward
|> Wei.to(:wei)
|> Decimal.add(Decimal.new(3))
|> Wei.from(:wei)
assert expected == Chain.block_reward(block.number)
end
test "with block without transactions", %{block: block, emission_reward: emission_reward} do
assert emission_reward.reward == Chain.block_reward(block.number)
end
end
describe "gas_payment_by_block_hash/1" do
setup do
number = 1
%{consensus_block: insert(:block, number: number, consensus: true), number: number}
end
test "without consensus block hash has no key", %{consensus_block: consensus_block, number: number} do
non_consensus_block = insert(:block, number: number, consensus: false)
:transaction
|> insert(gas_price: 1)
|> with_block(consensus_block, gas_used: 1)
:transaction
|> insert(gas_price: 1)
|> with_block(consensus_block, gas_used: 2)
assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{}
end
test "with consensus block hash without transactions has key with 0 value", %{
consensus_block: %Block{hash: consensus_block_hash}
} do
assert Chain.gas_payment_by_block_hash([consensus_block_hash]) == %{
consensus_block_hash => %Wei{value: Decimal.new(0)}
}
end
test "with consensus block hash with transactions has key with value", %{
consensus_block: %Block{hash: consensus_block_hash} = consensus_block
} do
:transaction
|> insert(gas_price: 1)
|> with_block(consensus_block, gas_used: 2)
:transaction
|> insert(gas_price: 3)
|> with_block(consensus_block, gas_used: 4)
assert Chain.gas_payment_by_block_hash([consensus_block_hash]) == %{
consensus_block_hash => %Wei{value: Decimal.new(14)}
}
end
end
describe "missing_block_number_ranges/1" do
# 0000
test "0..0 without blocks" do
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0001
test "0..0 with block 3" do
insert(:block, number: 3)
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0010
test "0..0 with block 2" do
insert(:block, number: 2)
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0011
test "0..0 with blocks 2,3" do
Enum.each([2, 3], &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0100
test "0..0 with block 1" do
insert(:block, number: 1)
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0101
test "0..0 with blocks 1,3" do
Enum.each([1, 3], fn num ->
insert(:block, number: num)
Process.sleep(200)
end)
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0111
test "0..0 with blocks 1..3" do
Enum.each(1..3, fn num ->
insert(:block, number: num)
Process.sleep(200)
end)
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 1000
test "0..0 with block 0" do
insert(:block, number: 0)
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1001
test "0..0 with blocks 0,3" do
Enum.each([0, 3], &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1010
test "0..0 with blocks 0,2" do
Enum.each([0, 2], &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1011
test "0..0 with blocks 0,2,3" do
Enum.each([0, 2, 3], &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1100
test "0..0 with blocks 0..1" do
Enum.each(0..1, &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1101
test "0..0 with blocks 0,1,3" do
Enum.each([0, 1, 3], fn num ->
insert(:block, number: num)
Process.sleep(200)
end)
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1110
test "0..0 with blocks 0..2" do
Enum.each(0..2, &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == []
end
# 1111
test "0..0 with blocks 0..3" do
Enum.each(0..2, fn num ->
insert(:block, number: num)
Process.sleep(200)
end)
assert Chain.missing_block_number_ranges(0..0) == []
end
test "0..2 with block 1" do
insert(:block, number: 1)
assert Chain.missing_block_number_ranges(0..2) == [0..0, 2..2]
end
end
describe "recent_collated_transactions/1" do
test "with no collated transactions it returns an empty list" do
assert [] == Explorer.Chain.recent_collated_transactions()
end
test "it excludes pending transactions" do
insert(:transaction)
assert [] == Explorer.Chain.recent_collated_transactions()
end
test "returns a list of recent collated transactions" do
newest_first_transactions =
50
|> insert_list(:transaction)
|> with_block()
|> Enum.reverse()
oldest_seen = Enum.at(newest_first_transactions, 9)
paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}}
recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options)
assert length(recent_collated_transactions) == 10
assert hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction =
:transaction
|> insert()
|> with_block()
insert_list(
2,
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions())
assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2
end
end
describe "smart_contract_bytecode/1" do
test "fetches the smart contract bytecode" do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address = insert(:address, contract_code: smart_contract_bytecode)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert Chain.smart_contract_bytecode(created_contract_address.hash) == smart_contract_bytecode
end
end
describe "create_decompiled_smart_contract/1" do
test "with valid params creates decompiled smart contract" do
address_hash = to_string(insert(:address).hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
params = %{
address_hash: address_hash,
decompiler_version: decompiler_version,
decompiled_source_code: decompiled_source_code
}
{:ok, decompiled_smart_contract} = Chain.create_decompiled_smart_contract(params)
assert decompiled_smart_contract.decompiler_version == decompiler_version
assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code
assert address_hash == to_string(decompiled_smart_contract.address_hash)
end
test "with invalid params can't create decompiled smart contract" do
params = %{code: "cat"}
{:error, _changeset} = Chain.create_decompiled_smart_contract(params)
end
test "updates smart contract code" do
inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
code = "code2"
{:ok, _decompiled_smart_contract} =
Chain.create_decompiled_smart_contract(%{
decompiler_version: inserted_decompiled_smart_contract.decompiler_version,
decompiled_source_code: code,
address_hash: inserted_decompiled_smart_contract.address_hash
})
decompiled_smart_contract =
Repo.one(
from(ds in DecompiledSmartContract,
where:
ds.address_hash == ^inserted_decompiled_smart_contract.address_hash and
ds.decompiler_version == ^inserted_decompiled_smart_contract.decompiler_version
)
)
assert decompiled_smart_contract.decompiled_source_code == code
end
test "creates two smart contracts for different decompiler versions" do
inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
code = "code2"
version = "2"
{:ok, _decompiled_smart_contract} =
Chain.create_decompiled_smart_contract(%{
decompiler_version: version,
decompiled_source_code: code,
address_hash: inserted_decompiled_smart_contract.address_hash
})
decompiled_smart_contracts =
Repo.all(
from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash)
)
assert Enum.count(decompiled_smart_contracts) == 2
end
end
describe "create_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
}
{:ok, valid_attrs: valid_attrs, address: created_contract_address}
end
test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert smart_contract.name == "SimpleStorage"
assert smart_contract.compiler_version == "0.4.23"
assert smart_contract.optimization == false
assert smart_contract.contract_source_code != ""
assert smart_contract.abi != ""
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do
insert(:address_name, address: address, primary: true)
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "trims whitespace from address name", %{valid_attrs: valid_attrs} do
attrs = %{valid_attrs | name: " SimpleStorage "}
assert {:ok, _} = Chain.create_smart_contract(attrs)
assert Repo.get_by(Address.Name, name: "SimpleStorage")
end
test "sets the address verified field to true", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true
end
end
describe "stream_unfetched_balances/2" do
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do
%Address{hash: miner_hash} = miner = insert(:address)
%Block{number: block_number} = insert(:block, miner: miner)
balance = insert(:unfetched_balance, address_hash: miner_hash, block_number: block_number)
assert {:ok, [%{address_hash: ^miner_hash, block_number: ^block_number}]} =
Chain.stream_unfetched_balances([], &[&1 | &2])
update_balance_value(balance, 1)
assert {:ok, []} = Chain.stream_unfetched_balances([], &[&1 | &2])
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Transaction.t/0` `from_address_hash`" do
%Address{hash: from_address_hash} = from_address = insert(:address)
%Block{number: block_number} = block = insert(:block)
:transaction
|> insert(from_address: from_address)
|> with_block(block)
balance = insert(:unfetched_balance, address_hash: from_address_hash, block_number: block_number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{address_hash: from_address_hash, block_number: block_number} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{address_hash: from_address_hash, block_number: block_number} in balance_fields_list
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Transaction.t/0` `to_address_hash`" do
%Address{hash: to_address_hash} = to_address = insert(:address)
%Block{number: block_number} = block = insert(:block)
:transaction
|> insert(to_address: to_address)
|> with_block(block)
balance = insert(:unfetched_balance, address_hash: to_address_hash, block_number: block_number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{address_hash: to_address_hash, block_number: block_number} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{address_hash: to_address_hash, block_number: block_number} in balance_fields_list
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Log.t/0` `address_hash`" do
address = insert(:address)
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(:log, address: address, transaction: transaction)
balance = insert(:unfetched_balance, address_hash: address.hash, block_number: block.number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{
address_hash: address.hash,
block_number: block.number
} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{
address_hash: address.hash,
block_number: block.number
} in balance_fields_list
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `created_contract_address_hash`" do
created_contract_address = insert(:address)
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:internal_transaction_create,
created_contract_address: created_contract_address,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
balance = insert(:unfetched_balance, address_hash: created_contract_address.hash, block_number: block.number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{
address_hash: created_contract_address.hash,
block_number: block.number
} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{
address_hash: created_contract_address.hash,
block_number: block.number
} in balance_fields_list
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `from_address_hash`" do
from_address = insert(:address)
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:internal_transaction_create,
from_address: from_address,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
balance = insert(:unfetched_balance, address_hash: from_address.hash, block_number: block.number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{address_hash: from_address.hash, block_number: block.number} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{address_hash: from_address.hash, block_number: block.number} in balance_fields_list
end
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `to_address_hash`" do
to_address = insert(:address)
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:internal_transaction_create,
to_address: to_address,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
balance = insert(:unfetched_balance, address_hash: to_address.hash, block_number: block.number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
assert %{address_hash: to_address.hash, block_number: block.number} in balance_fields_list
update_balance_value(balance, 1)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
refute %{address_hash: to_address.hash, block_number: block.number} in balance_fields_list
end
test "an address_hash used for multiple block_numbers returns all block_numbers" do
miner = insert(:address)
mined_block = insert(:block, miner: miner)
insert(:unfetched_balance, address_hash: miner.hash, block_number: mined_block.number)
from_transaction_block = insert(:block)
:transaction
|> insert(from_address: miner)
|> with_block(from_transaction_block)
insert(:unfetched_balance, address_hash: miner.hash, block_number: from_transaction_block.number)
to_transaction_block = insert(:block)
:transaction
|> insert(to_address: miner)
|> with_block(to_transaction_block)
insert(:unfetched_balance, address_hash: miner.hash, block_number: to_transaction_block.number)
log_block = insert(:block)
log_transaction =
:transaction
|> insert()
|> with_block(log_block)
insert(:log, address: miner, transaction: log_transaction)
insert(:unfetched_balance, address_hash: miner.hash, block_number: log_block.number)
from_internal_transaction_block = insert(:block)
from_internal_transaction_transaction =
:transaction
|> insert()
|> with_block(from_internal_transaction_block)
insert(
:internal_transaction_create,
from_address: miner,
index: 0,
transaction: from_internal_transaction_transaction,
block_number: from_internal_transaction_transaction.block_number,
block_hash: from_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: from_internal_transaction_transaction.index
)
insert(:unfetched_balance, address_hash: miner.hash, block_number: from_internal_transaction_block.number)
to_internal_transaction_block = insert(:block)
to_internal_transaction_transaction =
:transaction
|> insert()
|> with_block(to_internal_transaction_block)
insert(
:internal_transaction_create,
index: 0,
to_address: miner,
transaction: to_internal_transaction_transaction,
block_number: to_internal_transaction_transaction.block_number,
block_hash: to_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: to_internal_transaction_transaction.index
)
insert(:unfetched_balance, address_hash: miner.hash, block_number: to_internal_transaction_block.number)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
balance_fields_list_by_address_hash = Enum.group_by(balance_fields_list, & &1.address_hash)
assert balance_fields_list_by_address_hash[miner.hash] |> Enum.map(& &1.block_number) |> Enum.sort() ==
Enum.sort([
to_internal_transaction_block.number,
from_internal_transaction_block.number,
log_block.number,
to_transaction_block.number,
from_transaction_block.number,
mined_block.number
])
end
test "an address_hash used for the same block_number is only returned once" do
miner = insert(:address)
block = insert(:block, miner: miner)
insert(:unfetched_balance, address_hash: miner.hash, block_number: block.number)
:transaction
|> insert(from_address: miner)
|> with_block(block)
:transaction
|> insert(to_address: miner)
|> with_block(block)
log_transaction =
:transaction
|> insert()
|> with_block(block)
insert(:log, address: miner, transaction: log_transaction)
from_internal_transaction_transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:internal_transaction_create,
from_address: miner,
index: 0,
transaction: from_internal_transaction_transaction,
block_number: from_internal_transaction_transaction.block_number,
block_hash: from_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: from_internal_transaction_transaction.index
)
to_internal_transaction_transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:internal_transaction_create,
to_address: miner,
index: 0,
transaction: to_internal_transaction_transaction,
block_number: to_internal_transaction_transaction.block_number,
block_hash: to_internal_transaction_transaction.block_hash,
block_index: 1,
transaction_index: to_internal_transaction_transaction.index
)
{:ok, balance_fields_list} =
Explorer.Chain.stream_unfetched_balances(
[],
fn balance_fields, acc -> [balance_fields | acc] end
)
balance_fields_list_by_address_hash = Enum.group_by(balance_fields_list, & &1.address_hash)
assert balance_fields_list_by_address_hash[miner.hash] |> Enum.map(& &1.block_number) |> Enum.sort() == [
block.number
]
end
end
describe "update_replaced_transactions/2" do
test "update replaced transactions" do
replaced_transaction_hash = "0x2a263224a95275d77bc30a7e131bc64d948777946a790c0915ab293791fbcb61"
address = insert(:address, hash: "0xb7cffe2ac19b9d5705a24cbe14fef5663af905a6")
insert(:transaction,
from_address: address,
nonce: 1,
block_hash: nil,
index: nil,
block_number: nil,
hash: replaced_transaction_hash
)
mined_transaction_hash = "0x1a263224a95275d77bc30a7e131bc64d948777946a790c0915ab293791fbcb61"
block = insert(:block)
mined_transaction =
insert(:transaction,
from_address: address,
nonce: 1,
index: 0,
block_hash: block.hash,
block_number: block.number,
cumulative_gas_used: 1,
gas_used: 1,
hash: mined_transaction_hash
)
second_mined_transaction_hash = "0x3a263224a95275d77bc30a7e131bc64d948777946a790c0915ab293791fbcb61"
second_block = insert(:block)
insert(:transaction,
from_address: address,
nonce: 1,
index: 0,
block_hash: second_block.hash,
block_number: second_block.number,
cumulative_gas_used: 1,
gas_used: 1,
hash: second_mined_transaction_hash
)
{1, _} =
Chain.update_replaced_transactions([
%{
block_hash: mined_transaction.block_hash,
nonce: mined_transaction.nonce,
from_address_hash: mined_transaction.from_address_hash
}
])
replaced_transaction = Repo.get(Transaction, replaced_transaction_hash)
assert replaced_transaction.status == :error
assert replaced_transaction.error == "dropped/replaced"
found_mined_transaction = Repo.get(Transaction, mined_transaction_hash)
assert found_mined_transaction.status == nil
assert found_mined_transaction.error == nil
second_mined_transaction = Repo.get(Transaction, second_mined_transaction_hash)
assert second_mined_transaction.status == nil
assert second_mined_transaction.error == nil
end
end
describe "stream_unfetched_token_balances/2" do
test "executes the given reducer with the query result" do
address = insert(:address, hash: "0xc45e4830dff873cf8b70de2b194d0ddd06ef651e")
token_balance = insert(:token_balance, value_fetched_at: nil, address: address)
insert(:token_balance)
assert Chain.stream_unfetched_token_balances([], &[&1.block_number | &2]) == {:ok, [token_balance.block_number]}
end
end
describe "stream_unfetched_uncles/2" do
test "does not return uncle hashes where t:Explorer.Chain.Block.SecondDegreeRelation.t/0 uncle_fetched_at is not nil" do
%Block.SecondDegreeRelation{nephew: %Block{}, nephew_hash: nephew_hash, index: index, uncle_hash: uncle_hash} =
insert(:block_second_degree_relation)
assert {:ok, [%{nephew_hash: ^nephew_hash, index: ^index}]} =
Explorer.Chain.stream_unfetched_uncles([], &[&1 | &2])
query = from(bsdr in Block.SecondDegreeRelation, where: bsdr.uncle_hash == ^uncle_hash)
assert {1, _} = Repo.update_all(query, set: [uncle_fetched_at: DateTime.utc_now()])
assert {:ok, []} = Explorer.Chain.stream_unfetched_uncles([], &[&1 | &2])
end
end
test "total_supply/0" do
Application.put_env(:explorer, :supply, Explorer.Chain.Supply.ProofOfAuthority)
height = 2_000_000
insert(:block, number: height)
expected = ProofOfAuthority.initial_supply() + height
assert Chain.total_supply() == expected
end
test "circulating_supply/0" do
Application.put_env(:explorer, :supply, Explorer.Chain.Supply.ProofOfAuthority)
assert Chain.circulating_supply() == ProofOfAuthority.circulating()
end
describe "address_hash_to_smart_contract/1" do
test "fetches a smart contract" do
smart_contract = insert(:smart_contract)
assert ^smart_contract = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
end
end
describe "token_from_address_hash/1" do
test "with valid hash" do
token = insert(:token)
assert {:ok, result} = Chain.token_from_address_hash(token.contract_address.hash)
assert result.contract_address_hash == token.contract_address_hash
end
test "with hash that doesn't exist" do
token = build(:token)
assert {:error, :not_found} = Chain.token_from_address_hash(token.contract_address.hash)
end
test "with contract_address' smart_contract preloaded" do
smart_contract = build(:smart_contract)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options)
assert smart_contract = result.contract_address.smart_contract
end
end
test "stream_uncataloged_token_contract_address_hashes/2 reduces with given reducer and accumulator" do
insert(:token, cataloged: true)
%Token{contract_address_hash: uncatalog_address} = insert(:token, cataloged: false)
assert Chain.stream_uncataloged_token_contract_address_hashes([], &[&1 | &2]) == {:ok, [uncatalog_address]}
end
describe "stream_cataloged_token_contract_address_hashes/2" do
test "reduces with given reducer and accumulator" do
today = DateTime.utc_now()
yesterday = Timex.shift(today, days: -1)
%Token{contract_address_hash: catalog_address} = insert(:token, cataloged: true, updated_at: yesterday)
insert(:token, cataloged: false)
assert Chain.stream_cataloged_token_contract_address_hashes([], &[&1 | &2], 1) == {:ok, [catalog_address]}
end
test "sorts the tokens by updated_at in ascending order" do
today = DateTime.utc_now()
yesterday = Timex.shift(today, days: -1)
two_days_ago = Timex.shift(today, days: -2)
token1 = insert(:token, %{cataloged: true, updated_at: yesterday})
token2 = insert(:token, %{cataloged: true, updated_at: two_days_ago})
expected_response =
[token1, token2]
|> Enum.sort(&(Timex.to_unix(&1.updated_at) < Timex.to_unix(&2.updated_at)))
|> Enum.map(& &1.contract_address_hash)
assert Chain.stream_cataloged_token_contract_address_hashes([], &(&2 ++ [&1]), 12) == {:ok, expected_response}
end
end
describe "stream_unfetched_token_instances/2" do
test "reduces wuth given reducer and accumulator" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
token_transfer =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 11
)
assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
assert result.token_id == token_transfer.token_id
assert result.contract_address_hash == token_transfer.token_contract_address_hash
end
test "does not fetch token transfers without token id" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: nil
)
assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
end
test "do not fetch records with token instances" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
token_transfer =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 11
)
insert(:token_instance,
token_id: token_transfer.token_id,
token_contract_address_hash: token_transfer.token_contract_address_hash
)
assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
end
end
describe "search_token/1" do
test "finds by part of the name" do
token = insert(:token, name: "magic token", symbol: "MAGIC")
[result] = Chain.search_token("magic")
assert result.contract_address_hash == token.contract_address_hash
end
test "finds multiple results in different columns" do
insert(:token, name: "magic token", symbol: "TOKEN")
insert(:token, name: "token", symbol: "MAGIC")
result = Chain.search_token("magic")
assert Enum.count(result) == 2
end
test "do not returns wrong tokens" do
insert(:token, name: "token", symbol: "TOKEN")
result = Chain.search_token("magic")
assert Enum.empty?(result)
end
test "finds record by the term in the second word" do
insert(:token, name: "token magic", symbol: "TOKEN")
result = Chain.search_token("magic")
assert Enum.count(result) == 1
end
end
describe "transaction_has_token_transfers?/1" do
test "returns true if transaction has token transfers" do
transaction = insert(:transaction)
insert(:token_transfer, transaction: transaction)
assert Chain.transaction_has_token_transfers?(transaction.hash) == true
end
test "returns false if transaction has no token transfers" do
transaction = insert(:transaction)
assert Chain.transaction_has_token_transfers?(transaction.hash) == false
end
end
describe "update_token/2" do
test "updates a token's values" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
update_params = %{
name: "Hodl Token",
symbol: "HT",
total_supply: 10,
decimals: Decimal.new(1),
cataloged: true
}
assert {:ok, updated_token} = Chain.update_token(token, update_params)
assert updated_token.name == update_params.name
assert updated_token.symbol == update_params.symbol
assert updated_token.total_supply == Decimal.new(update_params.total_supply)
assert updated_token.decimals == update_params.decimals
assert updated_token.cataloged
end
test "trims names of whitespace" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
update_params = %{
name: " Hodl Token ",
symbol: "HT",
total_supply: 10,
decimals: 1,
cataloged: true
}
assert {:ok, updated_token} = Chain.update_token(token, update_params)
assert updated_token.name == "Hodl Token"
assert Repo.get_by(Address.Name, name: "Hodl Token")
end
test "inserts an address name record when token has a name in params" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
update_params = %{
name: "Hodl Token",
symbol: "HT",
total_supply: 10,
decimals: 1,
cataloged: true
}
Chain.update_token(token, update_params)
assert Repo.get_by(Address.Name, name: update_params.name, address_hash: token.contract_address_hash)
end
test "does not insert address name record when token doesn't have name in params" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
update_params = %{
cataloged: true
}
Chain.update_token(token, update_params)
refute Repo.get_by(Address.Name, address_hash: token.contract_address_hash)
end
test "stores token with big 'decimals' values" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
update_params = %{
name: "Hodl Token",
symbol: "HT",
total_supply: 10,
decimals: 1_000_000_000_000_000_000,
cataloged: true
}
assert {:ok, updated_token} = Chain.update_token(token, update_params)
end
end
describe "fetch_last_token_balances/1" do
test "returns the token balances given the address hash" do
address = insert(:address)
current_token_balance = insert(:address_current_token_balance, address: address)
insert(:address_current_token_balance, address: build(:address))
token_balances =
address.hash
|> Chain.fetch_last_token_balances()
|> Enum.map(& &1.address_hash)
assert token_balances == [current_token_balance.address_hash]
end
end
describe "fetch_token_holders_from_token_hash/2" do
test "returns the token holders" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
address_a = insert(:address)
address_b = insert(:address)
insert(
:address_current_token_balance,
address: address_a,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:address_current_token_balance,
address: address_b,
block_number: 1001,
token_contract_address_hash: contract_address_hash,
value: 4000
)
token_holders_count =
contract_address_hash
|> Chain.fetch_token_holders_from_token_hash([])
|> Enum.count()
assert token_holders_count == 2
end
end
describe "count_token_holders_from_token_hash" do
test "returns the most current count about token holders" do
address_a = insert(:address, hash: "0xe49fedd93960a0267b3c3b2c1e2d66028e013fee")
address_b = insert(:address, hash: "0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:address_current_token_balance,
address: address_a,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:address_current_token_balance,
address: address_b,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 1000
)
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 2
end
end
describe "address_to_transactions_with_token_transfers/2" do
test "paginates transactions by the block number" do
address = insert(:address)
token = insert(:token)
first_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 1000))
second_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 999))
insert(
:token_transfer,
to_address: address,
transaction: first_page,
token_contract_address: token.contract_address
)
insert(
:token_transfer,
to_address: address,
transaction: second_page,
token_contract_address: token.contract_address
)
paging_options = %PagingOptions{
key: {first_page.block_number, first_page.index},
page_size: 2
}
result =
address.hash
|> Chain.address_to_transactions_with_token_transfers(token.contract_address_hash,
paging_options: paging_options
)
|> Enum.map(& &1.hash)
assert result == [second_page.hash]
end
test "doesn't duplicate the transaction when there are multiple transfers for it" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
amount: 2,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
result =
address.hash
|> Chain.address_to_transactions_with_token_transfers(token.contract_address_hash)
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
end
describe "address_to_unique_tokens/2" do
test "unique tokens can be paginated through token_id" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
insert(
:token_instance,
token_contract_address_hash: token_contract_address.hash,
token_id: 11
)
insert(
:token_instance,
token_contract_address_hash: token_contract_address.hash,
token_id: 29
)
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
first_page =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 29
)
second_page =
insert(
:token_transfer,
block_number: 999,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 11
)
paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1}
unique_tokens_ids_paginated =
token_contract_address.hash
|> Chain.address_to_unique_tokens(paging_options: paging_options)
|> Enum.map(& &1.token_id)
assert unique_tokens_ids_paginated == [second_page.token_id]
end
end
describe "uncataloged_token_transfer_block_numbers/0" do
test "returns a list of block numbers" do
block = insert(:block)
address = insert(:address)
log =
insert(:token_transfer_log,
transaction:
insert(:transaction,
block_number: block.number,
block_hash: block.hash,
cumulative_gas_used: 0,
gas_used: 0,
index: 0
),
address_hash: address.hash
)
block_number = log.transaction.block_number
assert {:ok, [^block_number]} = Chain.uncataloged_token_transfer_block_numbers()
end
test "does not include transactions without a block_number" do
insert(:token_transfer_log)
assert {:ok, []} = Chain.uncataloged_token_transfer_block_numbers()
end
end
describe "address_to_balances_by_day/1" do
test "return a list of balances by day" do
address = insert(:address)
today = NaiveDateTime.utc_now()
noon = Timex.set(today, hour: 12)
block = insert(:block, timestamp: noon, number: 50)
yesterday = Timex.shift(noon, days: -1)
block_one_day_ago = insert(:block, timestamp: yesterday, number: 49)
insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block.number)
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: noon)
insert(:fetched_balance_daily, address_hash: address.hash, value: 2000, day: yesterday)
balances = Chain.address_to_balances_by_day(address.hash)
assert balances == [
%{date: yesterday |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("2E-15")},
%{date: today |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")}
]
end
test "adds todays entry" do
address = insert(:address)
today = NaiveDateTime.utc_now()
noon = Timex.set(today, hour: 12)
yesterday = Timex.shift(noon, days: -1)
block_one_day_ago = insert(:block, timestamp: yesterday)
insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block_one_day_ago.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: yesterday)
balances = Chain.address_to_balances_by_day(address.hash)
assert balances == [
%{date: yesterday |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")},
%{date: today |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")}
]
end
# Flaky test
# test "uses last block value if there a couple of change in the same day" do
# address = insert(:address)
# today = NaiveDateTime.utc_now()
# past = Timex.shift(today, hours: -1)
# block_now = insert(:block, timestamp: today, number: 1)
# insert(:fetched_balance, address_hash: address.hash, value: 1, block_number: block_now.number)
# block_past = insert(:block, timestamp: past, number: 2)
# insert(:fetched_balance, address_hash: address.hash, value: 0, block_number: block_past.number)
# insert(:fetched_balance_daily, address_hash: address.hash, value: 0, day: today)
# [balance] = Chain.address_to_balances_by_day(address.hash)
# assert balance.value == Decimal.new(0)
# end
end
describe "block_combined_rewards/1" do
test "sums the block_rewards values" do
block = insert(:block)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :validator,
reward: Decimal.new(1_000_000_000_000_000_000)
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :emission_funds,
reward: Decimal.new(1_000_000_000_000_000_000)
)
insert(
:reward,
address_hash: block.miner_hash,
block_hash: block.hash,
address_type: :uncle,
reward: Decimal.new(1_000_000_000_000_000_000)
)
block = Repo.preload(block, :rewards)
{:ok, expected_value} = Wei.cast(3_000_000_000_000_000_000)
assert Chain.block_combined_rewards(block) == expected_value
end
end
describe "contract_creation_input_data/1" do
test "fetches contract creation input data from contract creation transaction" do
address = insert(:address)
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
:transaction
|> insert(created_contract_address_hash: address.hash, input: input)
|> with_block()
found_creation_data = Chain.contract_creation_input_data(address.hash)
assert found_creation_data == Data.to_string(input)
end
test "fetches contract creation input data from internal transaction" do
created_contract_address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index,
input: input
)
assert Chain.contract_creation_input_data(created_contract_address.hash) == Data.to_string(input)
end
test "can't find address" do
hash = %Hash{
byte_count: 20,
bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
}
found_creation_data = Chain.contract_creation_input_data(hash)
assert found_creation_data == ""
end
test "fetches contract creation input data from contract byte code (if contract is pre-compiled)" do
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
address =
insert(:address,
contract_code: %Data{
bytes: <<1, 2, 3, 4, 5>>
}
)
found_creation_data = Chain.contract_creation_input_data(address.hash)
assert found_creation_data == Data.to_string(input) |> String.replace("0x", "")
end
end
describe "transaction_token_transfer_type/1" do
test "detects erc721 token transfer" do
from_address_hash = "0x7a30272c902563b712245696f0a81c5a0e45ddc8"
to_address_hash = "0xb544cead8b660aae9f2e37450f7be2ffbc501793"
from_address = insert(:address, hash: from_address_hash)
to_address = insert(:address, hash: to_address_hash)
block = insert(:block)
transaction =
insert(:transaction,
input:
"0x23b872dd0000000000000000000000007a30272c902563b712245696f0a81c5a0e45ddc8000000000000000000000000b544cead8b660aae9f2e37450f7be2ffbc5017930000000000000000000000000000000000000000000000000000000000000002",
value: Decimal.new(0),
created_contract_address_hash: nil
)
|> with_block(block, status: :ok)
insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction)
assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token))
end
test "detects erc20 token transfer" do
from_address_hash = "0x5881fdfE964bE26aC6C8e5153C4ad1c83181C024"
to_address_hash = "0xE113127804Ae2383f63Fe8cE31B212D5CB85113d"
from_address = insert(:address, hash: from_address_hash)
to_address = insert(:address, hash: to_address_hash)
block = insert(:block)
transaction =
insert(:transaction,
input:
"0xa9059cbb000000000000000000000000e113127804ae2383f63fe8ce31b212d5cb85113d0000000000000000000000000000000000000000000001b3093f45ba4dc40000",
value: Decimal.new(0),
created_contract_address_hash: nil
)
|> with_block(block, status: :ok)
insert(:token_transfer,
from_address: from_address,
to_address: to_address,
transaction: transaction,
amount: 8_025_000_000_000_000_000_000
)
assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token))
end
end
describe "contract_address?/2" do
test "returns true if address has contract code" do
code = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
address = insert(:address, contract_code: code)
assert Chain.contract_address?(to_string(address.hash), 1)
end
test "returns false if address has not contract code" do
address = insert(:address)
refute Chain.contract_address?(to_string(address.hash), 1)
end
@tag :no_parity
@tag :no_geth
test "returns true if fetched code from json rpc", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
hash = "0x71300d93a8CdF93385Af9635388cF2D00b95a480"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _arguments, _options ->
{:ok,
[
%{
id: 0,
result: "0x0102030405"
}
]}
end)
end
assert Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end
@tag :no_parity
@tag :no_geth
test "returns false if no fetched code from json rpc", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
hash = "0x71300d93a8CdF93385Af9635388cF2D00b95a480"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _arguments, _options ->
{:ok,
[
%{
id: 0,
result: "0x"
}
]}
end)
end
refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end
end
describe "staking_pools/3" do
test "validators staking pools" do
inserted_validator = insert(:staking_pool, is_active: true, is_validator: true)
insert(:staking_pool, is_active: true, is_validator: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [%{pool: gotten_validator}] = Chain.staking_pools(:validator, options)
assert inserted_validator.staking_address_hash == gotten_validator.staking_address_hash
end
test "active staking pools" do
inserted_pool = insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [%{pool: gotten_pool}] = Chain.staking_pools(:active, options)
assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end
test "all active staking pools ordered by staking_address" do
address1 = Factory.address_hash()
address2 = Factory.address_hash()
address3 = Factory.address_hash()
assert address1 < address2 and address2 < address3
# insert pools in descending order
insert(:staking_pool, is_active: true, staking_address_hash: address3)
insert(:staking_pool, is_active: true, staking_address_hash: address2)
insert(:staking_pool, is_active: true, staking_address_hash: address1)
# get all active pools in ascending order
assert [%{pool: pool1}, %{pool: pool2}, %{pool: pool3}] = Chain.staking_pools(:active, :all)
assert pool1.staking_address_hash == address1
assert pool2.staking_address_hash == address2
assert pool3.staking_address_hash == address3
end
test "staking pools ordered by stakes_ratio, is_active, and staking_address_hash" do
address1 = Factory.address_hash()
address2 = Factory.address_hash()
address3 = Factory.address_hash()
address4 = Factory.address_hash()
address5 = Factory.address_hash()
address6 = Factory.address_hash()
assert address1 < address2 and address2 < address3 and address3 < address4 and address4 < address5 and
address5 < address6
# insert pools in descending order
insert(:staking_pool, is_validator: true, is_active: false, staking_address_hash: address6, stakes_ratio: 0)
insert(:staking_pool, is_validator: true, is_active: false, staking_address_hash: address5, stakes_ratio: 0)
insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address4, stakes_ratio: 30)
insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address3, stakes_ratio: 60)
insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address2, stakes_ratio: 5)
insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address1, stakes_ratio: 5)
# get all pools in the order `desc: :stakes_ratio, desc: :is_active, asc: :staking_address_hash`
options = %PagingOptions{page_size: 20, page_number: 1}
assert [
%{pool: pool1},
%{pool: pool2},
%{pool: pool3},
%{pool: pool4},
%{pool: pool5},
%{pool: pool6}
] = Chain.staking_pools(:validator, options)
assert pool1.staking_address_hash == address3
assert pool2.staking_address_hash == address4
assert pool3.staking_address_hash == address1
assert pool4.staking_address_hash == address2
assert pool5.staking_address_hash == address5
assert pool6.staking_address_hash == address6
end
test "inactive staking pools" do
insert(:staking_pool, is_active: true)
inserted_pool = insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [%{pool: gotten_pool}] = Chain.staking_pools(:inactive, options)
assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end
end
describe "staking_pools_count/1" do
test "validators staking pools" do
insert(:staking_pool, is_active: true, is_validator: true)
insert(:staking_pool, is_active: true, is_validator: false)
assert Chain.staking_pools_count(:validator) == 1
end
test "active staking pools" do
insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:active) == 1
end
test "inactive staking pools" do
insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:inactive) == 1
end
end
describe "delegators_count_sum/1" do
test "validators pools" do
insert(:staking_pool, is_active: true, is_validator: true, delegators_count: 10)
insert(:staking_pool, is_active: true, is_validator: false, delegators_count: 7)
insert(:staking_pool, is_active: true, is_validator: true, delegators_count: 5)
assert Chain.delegators_count_sum(:validator) == 15
end
test "active staking pools" do
insert(:staking_pool, is_active: true, delegators_count: 10)
insert(:staking_pool, is_active: true, delegators_count: 7)
insert(:staking_pool, is_active: false, delegators_count: 5)
assert Chain.delegators_count_sum(:active) == 17
end
test "inactive staking pools" do
insert(:staking_pool, is_active: true, delegators_count: 10)
insert(:staking_pool, is_active: true, delegators_count: 7)
insert(:staking_pool, is_active: false, delegators_count: 5)
insert(:staking_pool, is_active: false, delegators_count: 1)
assert Chain.delegators_count_sum(:inactive) == 6
end
end
describe "total_staked_amount_sum/1" do
test "validators pools" do
insert(:staking_pool, is_active: true, is_validator: true, total_staked_amount: 10)
insert(:staking_pool, is_active: true, is_validator: false, total_staked_amount: 7)
insert(:staking_pool, is_active: true, is_validator: true, total_staked_amount: 5)
assert Chain.total_staked_amount_sum(:validator) == Decimal.new("15")
end
test "active staking pools" do
insert(:staking_pool, is_active: true, total_staked_amount: 10)
insert(:staking_pool, is_active: true, total_staked_amount: 7)
insert(:staking_pool, is_active: false, total_staked_amount: 5)
assert Chain.total_staked_amount_sum(:active) == Decimal.new("17")
end
test "inactive staking pools" do
insert(:staking_pool, is_active: true, total_staked_amount: 10)
insert(:staking_pool, is_active: true, total_staked_amount: 7)
insert(:staking_pool, is_active: false, total_staked_amount: 5)
insert(:staking_pool, is_active: false, total_staked_amount: 1)
assert Chain.total_staked_amount_sum(:inactive) == Decimal.new("6")
end
end
describe "extract_db_name/1" do
test "extracts correct db name" do
db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1"
assert Chain.extract_db_name(db_url) == "blockscout-dev-1"
end
test "returns empty db name" do
db_url = ""
assert Chain.extract_db_name(db_url) == ""
end
test "returns nil db name" do
db_url = nil
assert Chain.extract_db_name(db_url) == ""
end
end
describe "extract_db_host/1" do
test "extracts correct db host" do
db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1"
assert Chain.extract_db_host(db_url) == "localhost"
end
test "returns empty db name" do
db_url = ""
assert Chain.extract_db_host(db_url) == ""
end
test "returns nil db name" do
db_url = nil
assert Chain.extract_db_host(db_url) == ""
end
end
describe "fetch_first_trace/2" do
test "fetched first trace", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
gas = 4_533_872
init =
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef"
value = 0
block_number = 39
block_hash = "0x74c72ccabcb98b7ebbd7b31de938212b7e8814a002263b6569564e944d88f51f"
index = 0
created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461"
created_contract_code =
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029"
gas_used = 382_953
trace_address = []
transaction_hash = "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
transaction_index = 0
type = "create"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
result: %{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"from" => from_address_hash,
"gas" => integer_to_quantity(gas),
"init" => init,
"value" => integer_to_quantity(value)
},
"blockNumber" => block_number,
"index" => index,
"result" => %{
"address" => created_contract_address_hash,
"code" => created_contract_code,
"gasUsed" => integer_to_quantity(gas_used)
},
"traceAddress" => trace_address,
"type" => type
}
],
"transactionHash" => transaction_hash
}
}
]}
end)
end
{:ok, created_contract_address_hash_bytes} = Chain.string_to_address_hash(created_contract_address_hash)
{:ok, from_address_hash_bytes} = Chain.string_to_address_hash(from_address_hash)
{:ok, created_contract_code_bytes} = Data.cast(created_contract_code)
{:ok, init_bytes} = Data.cast(init)
{:ok, transaction_hash_bytes} = Chain.string_to_transaction_hash(transaction_hash)
{:ok, type_bytes} = Type.load(type)
value_wei = %Wei{value: Decimal.new(value)}
assert Chain.fetch_first_trace(
[
%{
hash_data: transaction_hash,
block_hash: block_hash,
block_number: block_number,
transaction_index: transaction_index
}
],
json_rpc_named_arguments
) == {
:ok,
[
%{
block_index: 0,
block_number: block_number,
block_hash: block_hash,
call_type: nil,
created_contract_address_hash: created_contract_address_hash_bytes,
created_contract_code: created_contract_code_bytes,
from_address_hash: from_address_hash_bytes,
gas: gas,
gas_used: gas_used,
index: index,
init: init_bytes,
input: nil,
output: nil,
to_address_hash: nil,
trace_address: trace_address,
transaction_hash: transaction_hash_bytes,
type: type_bytes,
value: value_wei,
transaction_index: transaction_index
}
]
}
end
end
describe "transaction_to_revert_reason/1" do
test "returns correct revert_reason from DB" do
transaction = insert(:transaction, revert_reason: "No credit of that type")
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end
test "returns correct revert_reason from the archive node" do
transaction =
insert(:transaction,
gas: 27319,
gas_price: "0x1b31d2900",
value: "0x86b3",
input: %Explorer.Chain.Data{bytes: <<1>>}
)
|> with_block(insert(:block, number: 1))
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _json, [] ->
{:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
end
)
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end
end
describe "proxy contracts features" do
@proxy_abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "bool", "name" => ""}],
"name" => "upgradeTo",
"inputs" => [%{"type" => "address", "name" => "newImplementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "renounceOwnership",
"inputs" => [],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getProxyStorage",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferOwnership",
"inputs" => [%{"type" => "address", "name" => "_newOwner"}],
"constant" => false
},
%{
"type" => "constructor",
"stateMutability" => "nonpayable",
"payable" => false,
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipRenounced",
"inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => true},
%{"type" => "address", "name" => "newOwner", "indexed" => true}
],
"anonymous" => false
}
]
@implementation_abi [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == []
end
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi
end
test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi)
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
end
test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == []
end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == []
end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi)
assert implementation_abi == @implementation_abi
end
test "get_implementation_abi/1 returns empty [] abi if implmentation address is null" do
assert Chain.get_implementation_abi(nil) == []
end
test "get_implementation_abi/1 returns [] if implementation is not verified" do
implementation_contract_address = insert(:contract_address)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == []
end
test "get_implementation_abi/1 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string)
assert implementation_abi == @implementation_abi
end
end
end