parent
da57a29b59
commit
29aa23e213
@ -0,0 +1,28 @@ |
||||
defmodule Explorer.Etherscan.Addresses do |
||||
@moduledoc """ |
||||
This module contains functions for working with addresses, as they pertain to the |
||||
`Explorer.Etherscan` context. |
||||
|
||||
""" |
||||
|
||||
import Ecto.Query, |
||||
only: [ |
||||
from: 2 |
||||
] |
||||
|
||||
alias Explorer.Chain.Address |
||||
alias Explorer.Repo |
||||
|
||||
@spec list_ordered_addresses(non_neg_integer(), non_neg_integer()) :: [Address.t()] |
||||
def list_ordered_addresses(offset, limit) do |
||||
query = |
||||
from( |
||||
address in Address, |
||||
order_by: [asc: address.inserted_at], |
||||
offset: ^offset, |
||||
limit: ^limit |
||||
) |
||||
|
||||
Repo.replica().all(query) |
||||
end |
||||
end |
@ -0,0 +1,95 @@ |
||||
defmodule Explorer.Etherscan.Blocks do |
||||
@moduledoc """ |
||||
This module contains functions for working with blocks, as they pertain to the |
||||
`Explorer.Etherscan` context. |
||||
|
||||
""" |
||||
|
||||
import Ecto.Query, |
||||
only: [ |
||||
from: 2 |
||||
] |
||||
|
||||
alias Explorer.{Chain, Repo} |
||||
alias Explorer.Chain.{Address.CoinBalance, Block, Hash, Wei} |
||||
|
||||
@doc """ |
||||
Returns the balance of the given address and block combination. |
||||
|
||||
Returns `{:error, :not_found}` if there is no address by that hash present. |
||||
Returns `{:error, :no_balance}` if there is no balance for that address at that block. |
||||
""" |
||||
@spec get_balance_as_of_block(Hash.Address.t(), Block.block_number() | :earliest | :latest | :pending) :: |
||||
{:ok, Wei.t()} | {:error, :no_balance} | {:error, :not_found} |
||||
def get_balance_as_of_block(address, block) when is_integer(block) do |
||||
coin_balance_query = |
||||
from(coin_balance in CoinBalance, |
||||
where: coin_balance.address_hash == ^address, |
||||
where: not is_nil(coin_balance.value), |
||||
where: coin_balance.block_number <= ^block, |
||||
order_by: [desc: coin_balance.block_number], |
||||
limit: 1, |
||||
select: coin_balance.value |
||||
) |
||||
|
||||
case Repo.replica().one(coin_balance_query) do |
||||
nil -> {:error, :not_found} |
||||
coin_balance -> {:ok, coin_balance} |
||||
end |
||||
end |
||||
|
||||
def get_balance_as_of_block(address, :latest) do |
||||
case Chain.max_consensus_block_number() do |
||||
{:ok, latest_block_number} -> |
||||
get_balance_as_of_block(address, latest_block_number) |
||||
|
||||
{:error, :not_found} -> |
||||
{:error, :not_found} |
||||
end |
||||
end |
||||
|
||||
def get_balance_as_of_block(address, :earliest) do |
||||
query = |
||||
from(coin_balance in CoinBalance, |
||||
where: coin_balance.address_hash == ^address, |
||||
where: not is_nil(coin_balance.value), |
||||
where: coin_balance.block_number == 0, |
||||
limit: 1, |
||||
select: coin_balance.value |
||||
) |
||||
|
||||
case Repo.replica().one(query) do |
||||
nil -> {:error, :not_found} |
||||
coin_balance -> {:ok, coin_balance} |
||||
end |
||||
end |
||||
|
||||
def get_balance_as_of_block(address, :pending) do |
||||
query = |
||||
case Chain.max_consensus_block_number() do |
||||
{:ok, latest_block_number} -> |
||||
from(coin_balance in CoinBalance, |
||||
where: coin_balance.address_hash == ^address, |
||||
where: not is_nil(coin_balance.value), |
||||
where: coin_balance.block_number > ^latest_block_number, |
||||
order_by: [desc: coin_balance.block_number], |
||||
limit: 1, |
||||
select: coin_balance.value |
||||
) |
||||
|
||||
{:error, :not_found} -> |
||||
from(coin_balance in CoinBalance, |
||||
where: coin_balance.address_hash == ^address, |
||||
where: not is_nil(coin_balance.value), |
||||
order_by: [desc: coin_balance.block_number], |
||||
limit: 1, |
||||
select: coin_balance.value |
||||
) |
||||
end |
||||
|
||||
case Repo.replica().one(query) do |
||||
nil -> {:error, :not_found} |
||||
coin_balance -> {:ok, coin_balance} |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,167 @@ |
||||
defmodule Explorer.Etherscan.Contracts do |
||||
@moduledoc """ |
||||
This module contains functions for working with contracts, as they pertain to the |
||||
`Explorer.Etherscan` context. |
||||
|
||||
""" |
||||
|
||||
import Ecto.Query, |
||||
only: [ |
||||
from: 2 |
||||
] |
||||
|
||||
alias Explorer.{Chain, Repo} |
||||
alias Explorer.Chain.{Address, Hash, SmartContract} |
||||
|
||||
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil |
||||
def address_hash_to_address_with_source_code(address_hash) do |
||||
case Repo.replica().get(Address, address_hash) do |
||||
nil -> |
||||
nil |
||||
|
||||
address -> |
||||
address_with_smart_contract = |
||||
Repo.replica().preload(address, [ |
||||
:smart_contract, |
||||
:decompiled_smart_contracts, |
||||
:smart_contract_additional_sources |
||||
]) |
||||
|
||||
if address_with_smart_contract.smart_contract do |
||||
formatted_code = format_source_code_output(address_with_smart_contract.smart_contract) |
||||
|
||||
%{ |
||||
address_with_smart_contract |
||||
| smart_contract: %{address_with_smart_contract.smart_contract | contract_source_code: formatted_code} |
||||
} |
||||
else |
||||
address_verified_twin_contract = |
||||
Chain.get_minimal_proxy_template(address_hash) || |
||||
Chain.get_address_verified_twin_contract(address_hash).verified_contract |
||||
|
||||
if address_verified_twin_contract do |
||||
formatted_code = format_source_code_output(address_verified_twin_contract) |
||||
|
||||
%{ |
||||
address_with_smart_contract |
||||
| smart_contract: %{address_verified_twin_contract | contract_source_code: formatted_code} |
||||
} |
||||
else |
||||
address_with_smart_contract |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
def list_verified_contracts(limit, offset) do |
||||
query = |
||||
from( |
||||
smart_contract in SmartContract, |
||||
order_by: [asc: smart_contract.inserted_at], |
||||
limit: ^limit, |
||||
offset: ^offset, |
||||
preload: [:address] |
||||
) |
||||
|
||||
query |
||||
|> Repo.replica().all() |
||||
|> Enum.map(fn smart_contract -> |
||||
Map.put(smart_contract.address, :smart_contract, smart_contract) |
||||
end) |
||||
end |
||||
|
||||
def list_decompiled_contracts(limit, offset, not_decompiled_with_version \\ nil) do |
||||
query = |
||||
from( |
||||
address in Address, |
||||
where: address.contract_code != <<>>, |
||||
where: not is_nil(address.contract_code), |
||||
where: address.decompiled == true, |
||||
limit: ^limit, |
||||
offset: ^offset, |
||||
order_by: [asc: address.inserted_at], |
||||
preload: [:smart_contract] |
||||
) |
||||
|
||||
query |
||||
|> reject_decompiled_with_version(not_decompiled_with_version) |
||||
|> Repo.replica().all() |
||||
end |
||||
|
||||
def list_unordered_unverified_contracts(limit, offset) do |
||||
query = |
||||
from( |
||||
address in Address, |
||||
where: address.contract_code != <<>>, |
||||
where: not is_nil(address.contract_code), |
||||
where: fragment("? IS NOT TRUE", address.verified), |
||||
limit: ^limit, |
||||
offset: ^offset |
||||
) |
||||
|
||||
query |
||||
|> Repo.replica().all() |
||||
|> Enum.map(fn address -> |
||||
%{address | smart_contract: nil} |
||||
end) |
||||
end |
||||
|
||||
def list_unordered_not_decompiled_contracts(limit, offset) do |
||||
query = |
||||
from( |
||||
address in Address, |
||||
where: fragment("? IS NOT TRUE", address.verified), |
||||
where: fragment("? IS NOT TRUE", address.decompiled), |
||||
where: address.contract_code != <<>>, |
||||
where: not is_nil(address.contract_code), |
||||
limit: ^limit, |
||||
offset: ^offset |
||||
) |
||||
|
||||
query |
||||
|> Repo.replica().all() |
||||
|> Enum.map(fn address -> |
||||
%{address | smart_contract: nil} |
||||
end) |
||||
end |
||||
|
||||
def list_empty_contracts(limit, offset) do |
||||
query = |
||||
from(address in Address, |
||||
where: address.contract_code == <<>>, |
||||
preload: [:smart_contract, :decompiled_smart_contracts], |
||||
order_by: [asc: address.inserted_at], |
||||
limit: ^limit, |
||||
offset: ^offset |
||||
) |
||||
|
||||
Repo.replica().all(query) |
||||
end |
||||
|
||||
def list_contracts(limit, offset) do |
||||
query = |
||||
from( |
||||
address in Address, |
||||
where: not is_nil(address.contract_code), |
||||
preload: [:smart_contract], |
||||
order_by: [asc: address.inserted_at], |
||||
limit: ^limit, |
||||
offset: ^offset |
||||
) |
||||
|
||||
Repo.replica().all(query) |
||||
end |
||||
|
||||
defp format_source_code_output(smart_contract), do: smart_contract.contract_source_code |
||||
|
||||
defp reject_decompiled_with_version(query, nil), do: query |
||||
|
||||
defp reject_decompiled_with_version(query, reject_version) do |
||||
from( |
||||
address in query, |
||||
left_join: decompiled_smart_contract in assoc(address, :decompiled_smart_contracts), |
||||
on: decompiled_smart_contract.decompiler_version == ^reject_version, |
||||
where: is_nil(decompiled_smart_contract.address_hash) |
||||
) |
||||
end |
||||
end |
@ -0,0 +1,40 @@ |
||||
defmodule Explorer.Etherscan.RPC do |
||||
@moduledoc """ |
||||
This module contains functions for working with mimicking of ETH RPC. |
||||
|
||||
""" |
||||
|
||||
import Ecto.Query, |
||||
only: [ |
||||
from: 2 |
||||
] |
||||
|
||||
alias Explorer.{Chain, Repo} |
||||
alias Explorer.Chain.Block |
||||
|
||||
@spec max_non_consensus_block_number(integer | nil) :: {:ok, Block.block_number()} | {:error, :not_found} |
||||
def max_non_consensus_block_number(max_consensus_block_number \\ nil) do |
||||
max = |
||||
if max_consensus_block_number do |
||||
{:ok, max_consensus_block_number} |
||||
else |
||||
Chain.max_consensus_block_number() |
||||
end |
||||
|
||||
case max do |
||||
{:ok, number} -> |
||||
query = |
||||
from(block in Block, |
||||
where: block.consensus == false, |
||||
where: block.number > ^number |
||||
) |
||||
|
||||
query |
||||
|> Repo.replica().aggregate(:max, :number) |
||||
|> case do |
||||
nil -> {:error, :not_found} |
||||
number -> {:ok, number} |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue