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

3704 lines
118 KiB

defmodule Explorer.ChainTest do
use Explorer.DataCase
use EthereumJSONRPC.Case, async: true
require Ecto.Query
import Ecto.Query
import Explorer.Factory
import Mox
alias Explorer.{Chain, Factory, PagingOptions, Repo}
alias Explorer.Chain.{
Address,
Block,
Data,
Hash,
InternalTransaction,
Log,
Token,
TokenTransfer,
Transaction,
SmartContract,
Wei
}
alias Explorer.Chain.Supply.ProofOfAuthority
alias Explorer.Counters.AddressesWithBalanceCounter
doctest Explorer.Chain
setup :set_mox_global
setup :verify_on_exit!
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_to_transactions_with_rewards/2" do
test "without transactions" do
address = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert [] == Chain.address_to_transactions_with_rewards(address)
end
test "with from transactions" do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions_with_rewards(address, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to transactions" do
address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions_with_rewards(address, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :from" do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
# only contains "from" transaction
assert [transaction] ==
Chain.address_to_transactions_with_rewards(address, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :to" do
address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions_with_rewards(address, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and no :direction option" do
address = insert(:address)
block = insert(:block)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block(block)
|> Repo.preload(:token_transfers)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block(block)
|> Repo.preload(:token_transfers)
assert [transaction2, transaction1] ==
Chain.address_to_transactions_with_rewards(address)
|> 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,
transaction_index: transaction.index
)
assert [] == Chain.address_to_transactions_with_rewards(address)
end
test "returns transactions that have token transfers for the given to_address" do
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_transactions_with_rewards(address)
|> Enum.map(& &1.hash)
end
test "returns just the token transfers related to the given address" do
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
|> Chain.address_to_transactions_with_rewards()
|> 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 "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
|> Chain.address_to_transactions_with_rewards()
|> 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 all token transfers when the given address is the token contract address" do
contract_address = insert(:address, contract_code: Factory.data("contract_code"))
transaction =
:transaction
|> insert(to_address: contract_address)
|> with_block()
insert(
:token_transfer,
to_address: build(:address),
token_contract_address: contract_address,
transaction: transaction
)
insert(
:token_transfer,
to_address: build(:address),
token_contract_address: contract_address,
transaction: transaction
)
transaction = Chain.address_to_transactions_with_rewards(contract_address) |> List.first()
assert Enum.count(transaction.token_transfers) == 2
end
test "returns all transactions that the address is present only in the token transfers" do
john = insert(:address)
paul = insert(:address)
contract_address = insert(:contract_address)
transaction_one =
:transaction
|> insert(
from_address: john,
from_address_hash: john.hash,
to_address: contract_address,
to_address_hash: contract_address.hash
)
|> with_block()
insert(
:token_transfer,
from_address: john,
to_address: paul,
transaction: transaction_one,
amount: 1
)
transaction_two =
:transaction
|> insert(
from_address: john,
from_address_hash: john.hash,
to_address: contract_address,
to_address_hash: contract_address.hash
)
|> with_block()
insert(
:token_transfer,
from_address: john,
to_address: paul,
transaction: transaction_two,
amount: 1
)
transactions_hashes =
paul
|> Chain.address_to_transactions_with_rewards()
|> Enum.map(& &1.hash)
assert Enum.member?(transactions_hashes, transaction_one.hash) == true
assert Enum.member?(transactions_hashes, transaction_two.hash) == true
end
test "with transactions can be paginated" do
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
|> Chain.address_to_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 = 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
|> Chain.address_to_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)
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_transactions_with_rewards(block.miner)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
end
test "with emission rewards and transactions" 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_transactions_with_rewards(block.miner, 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_transactions_with_rewards(block.miner)
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) == 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) == 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)
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)
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
|> 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))
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) == 0
end
test "with transactions" do
%Transaction{block: block} =
:transaction
|> insert()
|> with_block()
assert Chain.block_to_transaction_count(block) == 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, internal_transactions_indexed_at: DateTime.utc_now())
assert Chain.finished_indexing?()
end
test "not finished indexing" do
block = insert(:block, number: 1)
:transaction
|> insert()
|> with_block(block)
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,
transaction_index: transaction.index
)
Enum.each(1..3, fn index ->
insert(:internal_transaction_create,
transaction: transaction,
index: index,
block_number: transaction.block_number,
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
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)
end
assert Decimal.cmp(Chain.indexed_ratio(), 1) == :eq
end
end
describe "fetch_min_and_max_block_numbers/0" do
test "fetches min and max block numbers" do
for index <- 5..9 do
insert(:block, number: index)
end
assert {5, 9} = Chain.fetch_min_and_max_block_numbers()
end
test "fetches min and max when there are no blocks" do
assert {0, 0} = Chain.fetch_min_and_max_block_numbers()
end
test "fetches min and max where there is only one block" do
insert(:block, number: 1)
assert {1, 1} = Chain.fetch_min_and_max_block_numbers()
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"
}
]
},
broadcast: true,
internal_transactions: %{
params: [
%{
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: "0x",
output: "0x",
value: 0
}
]
},
logs: %{
params: [
%{
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: [
%{
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: [
%{
index: 0,
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>>
}
}
],
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>>
},
# because there are successful, non-contract-creation token transfer
internal_transactions_indexed_at: %DateTime{}
}
],
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
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
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 = insert(:address)
assert [] = Chain.get_blocks_validated_by_address(address)
end
test "returns the blocks validated by a specified address" do
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
|> Chain.get_blocks_validated_by_address()
|> Enum.map(& &1.hash)
assert results == [block.hash]
end
test "with blocks can be paginated" do
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)
|> 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)
|> 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,
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,
transaction_index: transaction.index
)
result =
address
|> 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 = 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,
transaction_index: transaction.index
)
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{}
}
| _
] = Chain.address_to_internal_transactions(address)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: %Address{},
transaction: %Transaction{}
}
| _
] =
Chain.address_to_internal_transactions(
address,
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,
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,
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,
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,
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,
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,
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,
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,
transaction_index: first_b_transaction.index
)
result =
address
|> 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)
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
index: 1
)
insert(
:internal_transaction,
transaction: pending_transaction,
to_address: address,
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,
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,
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,
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,
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,
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,
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
|> 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
|> 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
|> 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
|> 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 = 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,
transaction_index: transaction.index
)
assert Enum.empty?(Chain.address_to_internal_transactions(address))
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
expected =
insert(
:internal_transaction_create,
index: 0,
from_address: address,
transaction: transaction,
block_number: transaction.block_number,
transaction_index: transaction.index
)
actual = Enum.at(Chain.address_to_internal_transactions(address), 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)
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,
transaction_index: transaction.index
)
second =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction)
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 = insert(:transaction)
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{}
}
] = Chain.transaction_to_internal_transactions(transaction)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: nil,
transaction: %Transaction{}
}
] =
Chain.transaction_to_internal_transactions(
transaction,
necessity_by_association: %{
from_address: :optional,
to_address: :optional,
transaction: :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,
transaction_index: transaction.index
)
result = Chain.transaction_to_internal_transactions(transaction)
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,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 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,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 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,
transaction_index: transaction.index
)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 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: first_transaction_hash, index: first_index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
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,
transaction_index: transaction.index
)
result =
transaction
|> Chain.transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] == result
end
test "pages by index" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
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,
transaction_index: transaction.index
)
assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] ==
transaction
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{first_transaction_hash, first_index}] ==
transaction
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{second_transaction_hash, second_index}] ==
transaction
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, 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)
end
test "with logs" do
transaction =
:transaction
|> insert()
|> with_block()
%Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction)
assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction)
end
test "with logs can be paginated" do
transaction =
:transaction
|> insert()
|> with_block()
log = insert(:log, transaction: transaction, index: 1)
second_page_indexes =
2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end)
|> Enum.map(& &1.index)
assert second_page_indexes ==
transaction
|> 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)
assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs(
transaction,
necessity_by_association: %{
address: :optional,
transaction: :optional
}
)
assert [
%Log{
address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_logs(transaction)
end
end
describe "transaction_to_token_transfers/2" do
test "without token transfers" do
transaction = insert(:transaction)
assert [] = Chain.transaction_to_token_transfers(transaction)
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)
assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] =
Chain.transaction_to_token_transfers(transaction)
end
test "token transfers necessity_by_association loads associations" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:token_transfer, transaction: transaction)
assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] =
Chain.transaction_to_token_transfers(
transaction,
necessity_by_association: %{
token: :optional,
transaction: :optional
}
)
assert [
%TokenTransfer{
token: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_token_transfers(transaction)
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])
response = Chain.find_contract_address(address.hash)
assert response == {:ok, address}
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)
end
test "with block without transactions", %{block: block, emission_reward: emission_reward} do
assert emission_reward.reward == Chain.block_reward(block)
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], &insert(:block, number: &1))
assert Chain.missing_block_number_ranges(0..0) == [0..0]
end
# 0111
test "0..0 with blocks 1..3" do
Enum.each(1..3, &insert(:block, number: &1))
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], &insert(:block, number: &1))
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, &insert(:block, number: &1))
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,
transaction_index: transaction.index
)
assert Chain.smart_contract_bytecode(created_contract_address.hash) == smart_contract_bytecode
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,
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
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,
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,
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,
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,
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,
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,
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,
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_uncle_hashes/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{}, uncle_hash: uncle_hash} = insert(:block_second_degree_relation)
assert {:ok, [^uncle_hash]} = Explorer.Chain.stream_unfetched_uncle_hashes([], &[&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_uncle_hashes([], &[&1 | &2])
end
end
test "total_supply/0" do
height = 2_000_000
insert(:block, number: height)
expected = ProofOfAuthority.initial_supply() + height
assert Chain.total_supply() == expected
end
test "circulating_supply/0" do
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
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
%Token{contract_address_hash: catalog_address} = insert(:token, cataloged: true)
insert(:token, cataloged: false)
assert Chain.stream_cataloged_token_contract_address_hashes([], &[&1 | &2]) == {: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)
token1 = insert(:token, %{cataloged: true, updated_at: today})
token2 = insert(:token, %{cataloged: true, updated_at: yesterday})
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])) == {:ok, expected_response}
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")
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: 11
)
second_page =
insert(
:token_transfer,
block_number: 999,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 29
)
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)
noon = ~D[2018-12-06] |> Timex.to_datetime() |> Timex.set(hour: 12)
block = insert(:block, timestamp: noon)
block_one_day_ago = insert(:block, timestamp: Timex.shift(noon, days: -1))
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)
balances = Chain.address_to_balances_by_day(address.hash)
assert balances == [
%{date: "2018-12-05", value: Decimal.new("2E-15")},
%{date: "2018-12-06", value: Decimal.new("1E-15")}
]
end
end
describe "list_block_numbers_with_invalid_consensus/0" do
test "returns a list of block numbers with invalid consensus" do
block1 = insert(:block)
block2_with_invalid_consensus = insert(:block, parent_hash: block1.hash, consensus: false)
_block2 = insert(:block, parent_hash: block1.hash, number: block2_with_invalid_consensus.number)
block3 = insert(:block, parent_hash: block2_with_invalid_consensus.hash)
block4 = insert(:block, parent_hash: block3.hash)
block5 = insert(:block, parent_hash: block4.hash)
block6_without_consensus = insert(:block, parent_hash: block5.hash, consensus: false)
block6 = insert(:block, parent_hash: block5.hash, number: block6_without_consensus.number)
block7 = insert(:block, parent_hash: block6.hash)
block8_with_invalid_consensus = insert(:block, parent_hash: block7.hash, consensus: false)
_block8 = insert(:block, parent_hash: block7.hash, number: block8_with_invalid_consensus.number)
block9 = insert(:block, parent_hash: block8_with_invalid_consensus.hash)
_block10 = insert(:block, parent_hash: block9.hash)
assert Chain.list_block_numbers_with_invalid_consensus() ==
[block2_with_invalid_consensus.number, block8_with_invalid_consensus.number]
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,
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
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
end