commit
8da365b462
@ -0,0 +1,7 @@ |
||||
import $ from 'jquery' |
||||
import hljs from 'highlight.js' |
||||
|
||||
// only activate highlighting on pages with this selector
|
||||
if ($('[data-activate-highlight]').length > 0) { |
||||
hljs.initHighlightingOnLoad() |
||||
} |
@ -0,0 +1,55 @@ |
||||
defmodule BlockScoutWeb.TransactionRawTraceController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias BlockScoutWeb.TransactionView |
||||
alias Explorer.{Chain, Market} |
||||
alias Explorer.ExchangeRates.Token |
||||
|
||||
def index(conn, %{"transaction_id" => hash_string}) do |
||||
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), |
||||
{:ok, transaction} <- |
||||
Chain.hash_to_transaction( |
||||
hash, |
||||
necessity_by_association: %{ |
||||
:block => :optional, |
||||
[created_contract_address: :names] => :optional, |
||||
[from_address: :names] => :optional, |
||||
[to_address: :names] => :optional, |
||||
[to_address: :smart_contract] => :optional, |
||||
:token_transfers => :optional |
||||
} |
||||
) do |
||||
options = [ |
||||
necessity_by_association: %{ |
||||
[created_contract_address: :names] => :optional, |
||||
[from_address: :names] => :optional, |
||||
[to_address: :names] => :optional |
||||
} |
||||
] |
||||
|
||||
internal_transactions = Chain.transaction_to_internal_transactions(transaction, options) |
||||
|
||||
render( |
||||
conn, |
||||
"index.html", |
||||
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), |
||||
internal_transactions: internal_transactions, |
||||
block_height: Chain.block_height(), |
||||
show_token_transfers: Chain.transaction_has_token_transfers?(hash), |
||||
transaction: transaction |
||||
) |
||||
else |
||||
:error -> |
||||
conn |
||||
|> put_status(422) |
||||
|> put_view(TransactionView) |
||||
|> render("invalid.html", transaction_hash: hash_string) |
||||
|
||||
{:error, :not_found} -> |
||||
conn |
||||
|> put_status(404) |
||||
|> put_view(TransactionView) |
||||
|> render("not_found.html", transaction_hash: hash_string) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1 @@ |
||||
<%= render BlockScoutWeb.TransactionView, "_metatags.html", conn: @conn, transaction: @transaction %> |
@ -0,0 +1,18 @@ |
||||
<section class="container"> |
||||
<%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> |
||||
|
||||
<div class="card"> |
||||
<div class="card-header"> |
||||
<%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> |
||||
</div> |
||||
|
||||
<div class="card-body"> |
||||
<h2 class="card-title"><%= gettext "Raw Trace" %></h2> |
||||
<%= if Enum.count(@internal_transactions) > 0 do %> |
||||
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="json "><%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre> |
||||
<% else %> |
||||
No trace entries found. |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
</section> |
@ -0,0 +1,18 @@ |
||||
defmodule BlockScoutWeb.TransactionRawTraceView do |
||||
use BlockScoutWeb, :view |
||||
@dialyzer :no_match |
||||
|
||||
alias Explorer.Chain.InternalTransaction |
||||
|
||||
def render("scripts.html", %{conn: conn}) do |
||||
render_scripts(conn, "raw_trace/code_highlighting.js") |
||||
end |
||||
|
||||
def raw_traces_with_lines(internal_transactions) do |
||||
internal_transactions |
||||
|> InternalTransaction.internal_transactions_to_raw() |
||||
|> Jason.encode!(pretty: true) |
||||
|> String.split("\n") |
||||
|> Enum.with_index(1) |
||||
end |
||||
end |
@ -0,0 +1,282 @@ |
||||
defmodule EthereumJSONRPC.Geth.Tracer do |
||||
@moduledoc """ |
||||
Elixir implementation of a custom tracer (`priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js`) |
||||
for variants that don't support specifying tracer in [debug_traceTransaction](https://github.com/ethereum/go-ethereum/wiki/Management-APIs#debug_tracetransaction) calls. |
||||
""" |
||||
|
||||
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] |
||||
|
||||
def replay(%{"structLogs" => logs} = result, receipt, tx) when is_list(logs) do |
||||
%{"contractAddress" => contract_address} = receipt |
||||
%{"from" => from, "to" => to, "value" => value, "input" => input} = tx |
||||
|
||||
top = |
||||
to |
||||
|> if do |
||||
%{ |
||||
"type" => "call", |
||||
"callType" => "call", |
||||
"to" => to, |
||||
"input" => input, |
||||
"output" => Map.get(result, "return", "0x" <> Map.get(result, "returnValue", "")) |
||||
} |
||||
else |
||||
%{ |
||||
"type" => "create", |
||||
"init" => input, |
||||
"createdContractAddressHash" => contract_address, |
||||
"createdContractCode" => "0x" |
||||
} |
||||
end |
||||
|> Map.merge(%{ |
||||
"from" => from, |
||||
"traceAddress" => [], |
||||
"value" => value, |
||||
"gas" => 0, |
||||
"gasUsed" => 0 |
||||
}) |
||||
|
||||
ctx = %{ |
||||
depth: 1, |
||||
stack: [top], |
||||
trace_address: [0], |
||||
calls: [[]] |
||||
} |
||||
|
||||
logs |
||||
|> Enum.reduce(ctx, &step/2) |
||||
|> finalize() |
||||
end |
||||
|
||||
defp step(%{"error" => _}, %{stack: [%{"error" => _} | _]} = ctx), do: ctx |
||||
|
||||
defp step( |
||||
%{"error" => _} = log, |
||||
%{ |
||||
depth: stack_depth, |
||||
stack: [call | stack], |
||||
trace_address: [_, trace_index | trace_address], |
||||
calls: [subsubcalls, subcalls | calls] |
||||
} = ctx |
||||
) do |
||||
call = process_return(log, Map.put(call, "error", "error")) |
||||
|
||||
subsubcalls = |
||||
subsubcalls |
||||
|> Enum.reverse() |
||||
|> Enum.map(fn |
||||
subcalls when is_list(subcalls) -> subcalls |
||||
subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]} |
||||
end) |
||||
|
||||
%{ |
||||
ctx |
||||
| depth: stack_depth - 1, |
||||
stack: stack, |
||||
trace_address: [trace_index + 1 | trace_address], |
||||
calls: [[subsubcalls, call | subcalls] | calls] |
||||
} |
||||
end |
||||
|
||||
defp step( |
||||
%{"depth" => log_depth} = log, |
||||
%{ |
||||
depth: stack_depth, |
||||
stack: [call | stack], |
||||
trace_address: [_, trace_index | trace_address], |
||||
calls: [subsubcalls, subcalls | calls] |
||||
} = ctx |
||||
) |
||||
when log_depth == stack_depth - 1 do |
||||
call = process_return(log, call) |
||||
|
||||
subsubcalls = |
||||
subsubcalls |
||||
|> Enum.reverse() |
||||
|> Enum.map(fn |
||||
subcalls when is_list(subcalls) -> subcalls |
||||
subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]} |
||||
end) |
||||
|
||||
step(log, %{ |
||||
ctx |
||||
| depth: stack_depth - 1, |
||||
stack: stack, |
||||
trace_address: [trace_index + 1 | trace_address], |
||||
calls: [[subsubcalls, call | subcalls] | calls] |
||||
}) |
||||
end |
||||
|
||||
defp step(%{"gas" => log_gas, "gasCost" => log_gas_cost} = log, %{stack: [%{"gas" => call_gas} = call | stack]} = ctx) do |
||||
gas = max(call_gas, log_gas) |
||||
op(log, %{ctx | stack: [%{call | "gas" => gas, "gasUsed" => gas - log_gas - log_gas_cost} | stack]}) |
||||
end |
||||
|
||||
defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx) |
||||
defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx) |
||||
defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx) |
||||
defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx) |
||||
defp op(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx) |
||||
defp op(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx) |
||||
defp op(%{"op" => "REVERT"}, ctx), do: revert_op(ctx) |
||||
defp op(_, ctx), do: ctx |
||||
|
||||
defp process_return(%{"stack" => log_stack}, %{"type" => "create"} = call) do |
||||
[ret | _] = Enum.reverse(log_stack) |
||||
|
||||
case quantity_to_integer(ret) do |
||||
0 -> Map.put(call, "error", call["error"] || "internal failure") |
||||
_ -> %{call | "createdContractAddressHash" => "0x" <> String.slice(ret, 24, 40)} |
||||
end |
||||
end |
||||
|
||||
defp process_return( |
||||
%{"stack" => log_stack, "memory" => log_memory}, |
||||
%{"outputOffset" => out_off, "outputLength" => out_len} = call |
||||
) do |
||||
[ret | _] = Enum.reverse(log_stack) |
||||
|
||||
ret |
||||
|> quantity_to_integer() |
||||
|> case do |
||||
0 -> |
||||
Map.put(call, "error", call["error"] || "internal failure") |
||||
|
||||
_ -> |
||||
output = |
||||
log_memory |
||||
|> IO.iodata_to_binary() |
||||
|> String.slice(out_off, out_len) |
||||
|
||||
%{call | "output" => "0x" <> output} |
||||
end |
||||
|> Map.drop(["outputOffset", "outputLength"]) |
||||
end |
||||
|
||||
defp create_op( |
||||
%{"stack" => log_stack, "memory" => log_memory}, |
||||
%{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx |
||||
) do |
||||
[value, input_offset, input_length | _] = Enum.reverse(log_stack) |
||||
|
||||
init = |
||||
log_memory |
||||
|> IO.iodata_to_binary() |
||||
|> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2) |
||||
|
||||
call = %{ |
||||
"type" => "create", |
||||
"from" => nil, |
||||
"traceAddress" => Enum.reverse(trace_address), |
||||
"init" => "0x" <> init, |
||||
"gas" => 0, |
||||
"gasUsed" => 0, |
||||
"value" => "0x" <> value, |
||||
"createdContractAddressHash" => nil, |
||||
"createdContractCode" => "0x" |
||||
} |
||||
|
||||
%{ |
||||
ctx |
||||
| depth: stack_depth + 1, |
||||
stack: [call | stack], |
||||
trace_address: [0 | trace_address], |
||||
calls: [[] | calls] |
||||
} |
||||
end |
||||
|
||||
defp self_destruct_op( |
||||
%{"stack" => log_stack, "gas" => log_gas, "gasCost" => log_gas_cost}, |
||||
%{trace_address: [trace_index | trace_address], calls: [subcalls | calls]} = ctx |
||||
) do |
||||
[to | _] = Enum.reverse(log_stack) |
||||
|
||||
if quantity_to_integer(to) in 1..8 do |
||||
ctx |
||||
else |
||||
call = %{ |
||||
"type" => "selfdestruct", |
||||
"from" => nil, |
||||
"to" => "0x" <> String.slice(to, 24, 40), |
||||
"traceAddress" => Enum.reverse([trace_index | trace_address]), |
||||
"gas" => log_gas, |
||||
"gasUsed" => log_gas_cost, |
||||
"value" => "0x0" |
||||
} |
||||
|
||||
%{ctx | trace_address: [trace_index + 1 | trace_address], calls: [[call | subcalls] | calls]} |
||||
end |
||||
end |
||||
|
||||
defp call_op( |
||||
%{"stack" => log_stack, "memory" => log_memory}, |
||||
call_type, |
||||
%{ |
||||
depth: stack_depth, |
||||
stack: [%{"value" => parent_value} = parent | stack], |
||||
trace_address: trace_address, |
||||
calls: calls |
||||
} = ctx |
||||
) do |
||||
[_, to | log_stack] = Enum.reverse(log_stack) |
||||
|
||||
{value, [input_offset, input_length, output_offset, output_length | _]} = |
||||
case call_type do |
||||
"delegatecall" -> |
||||
{parent_value, log_stack} |
||||
|
||||
"staticcall" -> |
||||
{"0x0", log_stack} |
||||
|
||||
_ -> |
||||
[value | rest] = log_stack |
||||
{"0x" <> value, rest} |
||||
end |
||||
|
||||
input = |
||||
log_memory |
||||
|> IO.iodata_to_binary() |
||||
|> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2) |
||||
|
||||
call = %{ |
||||
"type" => "call", |
||||
"callType" => call_type, |
||||
"from" => nil, |
||||
"to" => "0x" <> String.slice(to, 24, 40), |
||||
"traceAddress" => Enum.reverse(trace_address), |
||||
"input" => "0x" <> input, |
||||
"output" => "0x", |
||||
"outputOffset" => quantity_to_integer("0x" <> output_offset) * 2, |
||||
"outputLength" => quantity_to_integer("0x" <> output_length) * 2, |
||||
"gas" => 0, |
||||
"gasUsed" => 0, |
||||
"value" => value |
||||
} |
||||
|
||||
%{ |
||||
ctx |
||||
| depth: stack_depth + 1, |
||||
stack: [call, parent | stack], |
||||
trace_address: [0 | trace_address], |
||||
calls: [[] | calls] |
||||
} |
||||
end |
||||
|
||||
defp revert_op(%{stack: [last | stack]} = ctx) do |
||||
%{ctx | stack: [Map.put(last, "error", "execution reverted") | stack]} |
||||
end |
||||
|
||||
defp finalize(%{stack: [top], calls: [calls]}) do |
||||
calls = |
||||
Enum.map(calls, fn |
||||
subcalls when is_list(subcalls) -> subcalls |
||||
subcall when is_map(subcall) -> %{subcall | "from" => top["createdContractAddressHash"] || top["to"]} |
||||
end) |
||||
|
||||
[top | Enum.reverse(calls)] |
||||
|> List.flatten() |
||||
|> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call -> |
||||
%{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)} |
||||
end) |
||||
end |
||||
end |
@ -0,0 +1,88 @@ |
||||
defmodule Explorer.Chain.Import.Runner.StakingPools do |
||||
@moduledoc """ |
||||
Bulk imports staking pools to Address.Name tabe. |
||||
""" |
||||
|
||||
require Ecto.Query |
||||
|
||||
alias Ecto.{Changeset, Multi, Repo} |
||||
alias Explorer.Chain.{Address, Import} |
||||
|
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
@behaviour Import.Runner |
||||
|
||||
# milliseconds |
||||
@timeout 60_000 |
||||
|
||||
@type imported :: [Address.Name.t()] |
||||
|
||||
@impl Import.Runner |
||||
def ecto_schema_module, do: Address.Name |
||||
|
||||
@impl Import.Runner |
||||
def option_key, do: :staking_pools |
||||
|
||||
@impl Import.Runner |
||||
def imported_table_row do |
||||
%{ |
||||
value_type: "[#{ecto_schema_module()}.t()]", |
||||
value_description: "List of `t:#{ecto_schema_module()}.t/0`s" |
||||
} |
||||
end |
||||
|
||||
@impl Import.Runner |
||||
def run(multi, changes_list, %{timestamps: timestamps} = options) do |
||||
insert_options = |
||||
options |
||||
|> Map.get(option_key(), %{}) |
||||
|> Map.take(~w(on_conflict timeout)a) |
||||
|> Map.put_new(:timeout, @timeout) |
||||
|> Map.put(:timestamps, timestamps) |
||||
|
||||
multi |
||||
|> Multi.run(:insert_staking_pools, fn repo, _ -> |
||||
insert(repo, changes_list, insert_options) |
||||
end) |
||||
end |
||||
|
||||
@impl Import.Runner |
||||
def timeout, do: @timeout |
||||
|
||||
@spec insert(Repo.t(), [map()], %{ |
||||
optional(:on_conflict) => Import.Runner.on_conflict(), |
||||
required(:timeout) => timeout, |
||||
required(:timestamps) => Import.timestamps() |
||||
}) :: |
||||
{:ok, [Address.Name.t()]} |
||||
| {:error, [Changeset.t()]} |
||||
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do |
||||
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) |
||||
|
||||
{:ok, _} = |
||||
Import.insert_changes_list( |
||||
repo, |
||||
changes_list, |
||||
conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, |
||||
on_conflict: on_conflict, |
||||
for: Address.Name, |
||||
returning: [:address_hash], |
||||
timeout: timeout, |
||||
timestamps: timestamps |
||||
) |
||||
end |
||||
|
||||
defp default_on_conflict do |
||||
from( |
||||
name in Address.Name, |
||||
update: [ |
||||
set: [ |
||||
name: fragment("EXCLUDED.name"), |
||||
metadata: fragment("EXCLUDED.metadata"), |
||||
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at), |
||||
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at) |
||||
] |
||||
] |
||||
) |
||||
end |
||||
end |
@ -0,0 +1,42 @@ |
||||
defmodule Explorer.Chain.InternalTransaction.Action do |
||||
@moduledoc """ |
||||
The action that was performed in a `t:EthereumJSONRPC.Parity.Trace.t/0` |
||||
""" |
||||
|
||||
import EthereumJSONRPC, only: [integer_to_quantity: 1] |
||||
alias Explorer.Chain.{Data, Hash, Wei} |
||||
|
||||
def to_raw(action) when is_map(action) do |
||||
Enum.into(action, %{}, &entry_to_raw/1) |
||||
end |
||||
|
||||
defp entry_to_raw({key, %Data{} = data}) when key in ~w(init input) do |
||||
{key, Data.to_string(data)} |
||||
end |
||||
|
||||
defp entry_to_raw({key, %Hash{} = address}) when key in ~w(address from refundAddress to) do |
||||
{key, to_string(address)} |
||||
end |
||||
|
||||
defp entry_to_raw({"callType", type}) do |
||||
{"callType", Atom.to_string(type)} |
||||
end |
||||
|
||||
defp entry_to_raw({"gas" = key, %Decimal{} = decimal}) do |
||||
value = |
||||
decimal |
||||
|> Decimal.round() |
||||
|> Decimal.to_integer() |
||||
|
||||
{key, integer_to_quantity(value)} |
||||
end |
||||
|
||||
defp entry_to_raw({key, %Wei{value: value}}) when key in ~w(balance value) do |
||||
rounded = |
||||
value |
||||
|> Decimal.round() |
||||
|> Decimal.to_integer() |
||||
|
||||
{key, integer_to_quantity(rounded)} |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
defmodule Explorer.Chain.InternalTransaction.Result do |
||||
@moduledoc """ |
||||
The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. |
||||
""" |
||||
|
||||
import EthereumJSONRPC, only: [integer_to_quantity: 1] |
||||
|
||||
alias Explorer.Chain.{Data, Hash} |
||||
|
||||
def to_raw(result) when is_map(result) do |
||||
Enum.into(result, %{}, &entry_to_raw/1) |
||||
end |
||||
|
||||
defp entry_to_raw({"output" = key, %Data{} = data}) do |
||||
{key, Data.to_string(data)} |
||||
end |
||||
|
||||
defp entry_to_raw({"address" = key, %Hash{} = hash}) do |
||||
{key, to_string(hash)} |
||||
end |
||||
|
||||
defp entry_to_raw({"code", _} = entry), do: entry |
||||
|
||||
defp entry_to_raw({key, decimal}) when key in ~w(gasUsed) do |
||||
integer = |
||||
decimal |
||||
|> Decimal.round() |
||||
|> Decimal.to_integer() |
||||
|
||||
{key, integer_to_quantity(integer)} |
||||
end |
||||
end |
@ -0,0 +1,121 @@ |
||||
defmodule Explorer.Staking.PoolsReader do |
||||
@moduledoc """ |
||||
Reads staking pools using Smart Contract functions from the blockchain. |
||||
""" |
||||
alias Explorer.SmartContract.Reader |
||||
|
||||
@spec get_pools() :: [String.t()] |
||||
def get_pools do |
||||
get_active_pools() ++ get_inactive_pools() |
||||
end |
||||
|
||||
@spec get_active_pools() :: [String.t()] |
||||
def get_active_pools do |
||||
{:ok, [active_pools]} = call_staking_method("getPools", []) |
||||
active_pools |
||||
end |
||||
|
||||
@spec get_inactive_pools() :: [String.t()] |
||||
def get_inactive_pools do |
||||
{:ok, [inactive_pools]} = call_staking_method("getPoolsInactive", []) |
||||
inactive_pools |
||||
end |
||||
|
||||
@spec pool_data(String.t()) :: {:ok, map()} | :error |
||||
def pool_data(staking_address) do |
||||
with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), |
||||
data = fetch_data(staking_address, mining_address), |
||||
{:ok, [is_active]} <- data["isPoolActive"], |
||||
{:ok, [delegator_addresses]} <- data["poolDelegators"], |
||||
delegators_count = Enum.count(delegator_addresses), |
||||
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], |
||||
{:ok, [is_validator]} <- data["isValidator"], |
||||
{:ok, [was_validator_count]} <- data["validatorCounter"], |
||||
{:ok, [is_banned]} <- data["isValidatorBanned"], |
||||
{:ok, [banned_until]} <- data["bannedUntil"], |
||||
{:ok, [was_banned_count]} <- data["banCounter"] do |
||||
{ |
||||
:ok, |
||||
%{ |
||||
staking_address: staking_address, |
||||
mining_address: mining_address, |
||||
is_active: is_active, |
||||
delegators_count: delegators_count, |
||||
staked_amount: staked_amount, |
||||
is_validator: is_validator, |
||||
was_validator_count: was_validator_count, |
||||
is_banned: is_banned, |
||||
banned_until: banned_until, |
||||
was_banned_count: was_banned_count |
||||
} |
||||
} |
||||
else |
||||
_ -> |
||||
:error |
||||
end |
||||
end |
||||
|
||||
defp call_staking_method(method, params) do |
||||
%{^method => resp} = |
||||
Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{ |
||||
method => params |
||||
}) |
||||
|
||||
resp |
||||
end |
||||
|
||||
defp call_validators_method(method, params) do |
||||
%{^method => resp} = |
||||
Reader.query_contract(config(:validators_contract_address), abi("validators.json"), %{ |
||||
method => params |
||||
}) |
||||
|
||||
resp |
||||
end |
||||
|
||||
defp fetch_data(staking_address, mining_address) do |
||||
contract_abi = abi("staking.json") ++ abi("validators.json") |
||||
|
||||
methods = [ |
||||
{:staking, "isPoolActive", staking_address}, |
||||
{:staking, "poolDelegators", staking_address}, |
||||
{:staking, "stakeAmountTotalMinusOrderedWithdraw", staking_address}, |
||||
{:validators, "isValidator", mining_address}, |
||||
{:validators, "validatorCounter", mining_address}, |
||||
{:validators, "isValidatorBanned", mining_address}, |
||||
{:validators, "bannedUntil", mining_address}, |
||||
{:validators, "banCounter", mining_address} |
||||
] |
||||
|
||||
methods |
||||
|> Enum.map(&format_request/1) |
||||
|> Reader.query_contracts(contract_abi) |
||||
|> Enum.zip(methods) |
||||
|> Enum.into(%{}, fn {response, {_, function_name, _}} -> |
||||
{function_name, response} |
||||
end) |
||||
end |
||||
|
||||
defp format_request({contract_name, function_name, param}) do |
||||
%{ |
||||
contract_address: contract(contract_name), |
||||
function_name: function_name, |
||||
args: [param] |
||||
} |
||||
end |
||||
|
||||
defp contract(:staking), do: config(:staking_contract_address) |
||||
defp contract(:validators), do: config(:validators_contract_address) |
||||
|
||||
defp config(key) do |
||||
Application.get_env(:explorer, __MODULE__, [])[key] |
||||
end |
||||
|
||||
# sobelow_skip ["Traversal"] |
||||
defp abi(file_name) do |
||||
:explorer |
||||
|> Application.app_dir("priv/contracts_abi/pos/#{file_name}") |
||||
|> File.read!() |
||||
|> Jason.decode!() |
||||
end |
||||
end |
@ -0,0 +1,925 @@ |
||||
[ |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "STAKE_UNIT", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "MAX_DELEGATORS_PER_POOL", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "MAX_CANDIDATES", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": true, |
||||
"name": "fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "staker", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "stakingEpoch", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "Claimed", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": true, |
||||
"name": "toPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "staker", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "stakingEpoch", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "Staked", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": false, |
||||
"name": "fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "toPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "staker", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "stakingEpoch", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "StakeMoved", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": true, |
||||
"name": "fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "staker", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "stakingEpoch", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "amount", |
||||
"type": "int256" |
||||
} |
||||
], |
||||
"name": "WithdrawalOrdered", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": true, |
||||
"name": "fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "staker", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"indexed": true, |
||||
"name": "stakingEpoch", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "Withdrawn", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_unremovableStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "clearUnremovableValidator", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [], |
||||
"name": "incrementStakingEpoch", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "removePool", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [], |
||||
"name": "removePool", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_toPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "moveStake", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_toPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "stake", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_fromPoolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "withdraw", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_amount", |
||||
"type": "int256" |
||||
} |
||||
], |
||||
"name": "orderWithdraw", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "claimOrderedWithdraw", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_erc20TokenContract", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "setErc20TokenContract", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_minStake", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "setCandidateMinStake", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": false, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_minStake", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "setDelegatorMinStake", |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPools", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPoolsInactive", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPoolsLikelihood", |
||||
"outputs": [ |
||||
{ |
||||
"name": "likelihoods", |
||||
"type": "int256[]" |
||||
}, |
||||
{ |
||||
"name": "sum", |
||||
"type": "int256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPoolsToBeElected", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPoolsToBeRemoved", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "areStakeAndWithdrawAllowed", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "erc20TokenContract", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getCandidateMinStake", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getDelegatorMinStake", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "isPoolActive", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "maxWithdrawAllowed", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "maxWithdrawOrderAllowed", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"name": "", |
||||
"type": "bytes" |
||||
} |
||||
], |
||||
"name": "onTokenTransfer", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "pure", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "orderedWithdrawAmount", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "orderedWithdrawAmountTotal", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "orderWithdrawEpoch", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakeAmountTotal", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolDelegators", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_delegator", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolDelegatorIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_delegator", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolDelegatorInactiveIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolInactiveIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolToBeElectedIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "poolToBeRemovedIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakeAmount", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakeAmountByCurrentEpoch", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_staker", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakeAmountMinusOrderedWithdraw", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_poolStakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakeAmountTotalMinusOrderedWithdraw", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "stakingEpoch", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "validatorSetContract", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
} |
||||
] |
@ -0,0 +1,492 @@ |
||||
[ |
||||
{ |
||||
"constant": false, |
||||
"inputs": [], |
||||
"name": "newValidatorSet", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
}, |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "MAX_VALIDATORS", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"indexed": true, |
||||
"name": "parentHash", |
||||
"type": "bytes32" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"name": "newSet", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"name": "InitiateChange", |
||||
"type": "event", |
||||
"anonymous": false |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "clearUnremovableValidator", |
||||
"type": "function", |
||||
"constant": false, |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "emitInitiateChange", |
||||
"type": "function", |
||||
"constant": false, |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "finalizeChange", |
||||
"type": "function", |
||||
"constant": false, |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"name": "_blockRewardContract", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_randomContract", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_stakingContract", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_initialMiningAddresses", |
||||
"type": "address[]" |
||||
}, |
||||
{ |
||||
"name": "_initialStakingAddresses", |
||||
"type": "address[]" |
||||
}, |
||||
{ |
||||
"name": "_firstValidatorIsUnremovable", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"name": "initialize", |
||||
"type": "function", |
||||
"constant": false, |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "setStakingAddress", |
||||
"type": "function", |
||||
"constant": false, |
||||
"outputs": [], |
||||
"payable": false, |
||||
"stateMutability": "nonpayable" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "banCounter", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "bannedUntil", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "blockRewardContract", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "changeRequestCount", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "emitInitiateChangeCallable", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPreviousValidators", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getPendingValidators", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getQueueValidators", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
}, |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "getValidators", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address[]" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "initiateChangeAllowed", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "isReportValidatorValid", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "isValidator", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "isValidatorOnPreviousEpoch", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "isValidatorBanned", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "miningByStakingAddress", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "randomContract", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "stakingByMiningAddress", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "stakingContract", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "unremovableValidator", |
||||
"outputs": [ |
||||
{ |
||||
"name": "stakingAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "validatorCounter", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [ |
||||
{ |
||||
"name": "_miningAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "validatorIndex", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"constant": true, |
||||
"inputs": [], |
||||
"name": "validatorSetApplyBlock", |
||||
"outputs": [ |
||||
{ |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"payable": false, |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
} |
||||
] |
@ -0,0 +1,94 @@ |
||||
defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do |
||||
use Explorer.DataCase |
||||
|
||||
alias Ecto.Multi |
||||
alias Explorer.Chain.Import.Runner.StakingPools |
||||
|
||||
describe "run/1" do |
||||
test "insert new pools list" do |
||||
pools = [ |
||||
%{ |
||||
address_hash: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> |
||||
}, |
||||
metadata: %{ |
||||
banned_unitil: 0, |
||||
delegators_count: 0, |
||||
is_active: true, |
||||
is_banned: false, |
||||
is_validator: true, |
||||
mining_address: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>> |
||||
}, |
||||
retries_count: 1, |
||||
staked_amount: 0, |
||||
was_banned_count: 0, |
||||
was_validator_count: 1 |
||||
}, |
||||
name: "anonymous", |
||||
primary: true |
||||
}, |
||||
%{ |
||||
address_hash: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>> |
||||
}, |
||||
metadata: %{ |
||||
banned_unitil: 0, |
||||
delegators_count: 0, |
||||
is_active: true, |
||||
is_banned: false, |
||||
is_validator: true, |
||||
mining_address: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>> |
||||
}, |
||||
retries_count: 1, |
||||
staked_amount: 0, |
||||
was_banned_count: 0, |
||||
was_validator_count: 1 |
||||
}, |
||||
name: "anonymous", |
||||
primary: true |
||||
}, |
||||
%{ |
||||
address_hash: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>> |
||||
}, |
||||
metadata: %{ |
||||
banned_unitil: 0, |
||||
delegators_count: 0, |
||||
is_active: true, |
||||
is_banned: false, |
||||
is_validator: true, |
||||
mining_address: %Explorer.Chain.Hash{ |
||||
byte_count: 20, |
||||
bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>> |
||||
}, |
||||
retries_count: 1, |
||||
staked_amount: 0, |
||||
was_banned_count: 0, |
||||
was_validator_count: 1 |
||||
}, |
||||
name: "anonymous", |
||||
primary: true |
||||
} |
||||
] |
||||
|
||||
assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) |
||||
assert Enum.count(list) == Enum.count(pools) |
||||
end |
||||
end |
||||
|
||||
defp run_changes(changes) do |
||||
Multi.new() |
||||
|> StakingPools.run(changes, %{ |
||||
timeout: :infinity, |
||||
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} |
||||
}) |
||||
|> Repo.transaction() |
||||
end |
||||
end |
@ -0,0 +1,238 @@ |
||||
defmodule Explorer.Token.PoolsReaderTest do |
||||
use EthereumJSONRPC.Case |
||||
use Explorer.DataCase |
||||
|
||||
alias Explorer.Staking.PoolsReader |
||||
|
||||
import Mox |
||||
|
||||
setup :verify_on_exit! |
||||
setup :set_mox_global |
||||
|
||||
describe "get_pools_list" do |
||||
test "get_active_pools success" do |
||||
get_pools_from_blockchain() |
||||
|
||||
result = PoolsReader.get_active_pools() |
||||
|
||||
assert Enum.count(result) == 3 |
||||
end |
||||
|
||||
test "get_active_pools error" do |
||||
fetch_from_blockchain_with_error() |
||||
|
||||
assert_raise MatchError, fn -> |
||||
PoolsReader.get_active_pools() |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe "get_pools_data" do |
||||
test "get_pool_data success" do |
||||
get_pool_data_from_blockchain() |
||||
|
||||
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> |
||||
|
||||
response = { |
||||
:ok, |
||||
%{ |
||||
banned_until: 0, |
||||
delegators_count: 0, |
||||
is_active: true, |
||||
is_banned: false, |
||||
is_validator: true, |
||||
mining_address: |
||||
<<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, |
||||
staked_amount: 0, |
||||
staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, |
||||
was_banned_count: 0, |
||||
was_validator_count: 2 |
||||
} |
||||
} |
||||
|
||||
assert PoolsReader.pool_data(address) == response |
||||
end |
||||
|
||||
test "get_pool_data error" do |
||||
fetch_from_blockchain_with_error() |
||||
|
||||
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> |
||||
|
||||
assert :error = PoolsReader.pool_data(address) |
||||
end |
||||
end |
||||
|
||||
defp get_pools_from_blockchain() do |
||||
expect( |
||||
EthereumJSONRPC.Mox, |
||||
:json_rpc, |
||||
fn [%{id: id, method: "eth_call", params: _}], _options -> |
||||
{:ok, |
||||
[ |
||||
%{ |
||||
id: id, |
||||
jsonrpc: "2.0", |
||||
result: |
||||
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba" |
||||
} |
||||
]} |
||||
end |
||||
) |
||||
end |
||||
|
||||
defp fetch_from_blockchain_with_error() do |
||||
expect( |
||||
EthereumJSONRPC.Mox, |
||||
:json_rpc, |
||||
fn [%{id: id, method: "eth_call", params: _}], _options -> |
||||
{:ok, |
||||
[ |
||||
%{ |
||||
error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."}, |
||||
id: id, |
||||
jsonrpc: "2.0" |
||||
} |
||||
]} |
||||
end |
||||
) |
||||
end |
||||
|
||||
defp get_pool_data_from_blockchain() do |
||||
expect( |
||||
EthereumJSONRPC.Mox, |
||||
:json_rpc, |
||||
2, |
||||
fn requests, _opts -> |
||||
{:ok, |
||||
Enum.map(requests, fn |
||||
# miningByStakingAddress |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" |
||||
} |
||||
|
||||
# isPoolActive |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
||||
} |
||||
|
||||
# poolDelegators |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: |
||||
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# stakeAmountTotalMinusOrderedWithdraw |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# isValidator |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
||||
} |
||||
|
||||
# validatorCounter |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000002" |
||||
} |
||||
|
||||
# isValidatorBanned |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# bannedUntil |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# banCounter |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
end)} |
||||
end |
||||
) |
||||
end |
||||
end |
@ -0,0 +1,136 @@ |
||||
defmodule Indexer.Fetcher.StakingPools do |
||||
@moduledoc """ |
||||
Fetches staking pools and send to be imported in `Address.Name` table |
||||
""" |
||||
|
||||
use Indexer.Fetcher |
||||
use Spandex.Decorators |
||||
|
||||
require Logger |
||||
|
||||
alias Explorer.Chain |
||||
alias Explorer.Staking.PoolsReader |
||||
alias Indexer.BufferedTask |
||||
alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor |
||||
|
||||
@behaviour BufferedTask |
||||
|
||||
@defaults [ |
||||
flush_interval: 300, |
||||
max_batch_size: 100, |
||||
max_concurrency: 10, |
||||
task_supervisor: Indexer.Fetcher.StakingPools.TaskSupervisor |
||||
] |
||||
|
||||
@max_retries 3 |
||||
|
||||
@spec async_fetch() :: :ok |
||||
def async_fetch do |
||||
if StakingPoolsSupervisor.disabled?() do |
||||
:ok |
||||
else |
||||
pools = |
||||
PoolsReader.get_pools() |
||||
|> Enum.map(&entry/1) |
||||
|
||||
BufferedTask.buffer(__MODULE__, pools, :infinity) |
||||
end |
||||
end |
||||
|
||||
@doc false |
||||
def child_spec([init_options, gen_server_options]) do |
||||
merged_init_opts = |
||||
@defaults |
||||
|> Keyword.merge(init_options) |
||||
|> Keyword.put(:state, {0, []}) |
||||
|
||||
Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) |
||||
end |
||||
|
||||
@impl BufferedTask |
||||
def init(_initial, reducer, acc) do |
||||
PoolsReader.get_pools() |
||||
|> Enum.map(&entry/1) |
||||
|> Enum.reduce(acc, &reducer.(&1, &2)) |
||||
end |
||||
|
||||
@impl BufferedTask |
||||
def run(pools, _json_rpc_named_arguments) do |
||||
failed_list = |
||||
pools |
||||
|> Enum.map(&Map.put(&1, :retries_count, &1.retries_count + 1)) |
||||
|> fetch_from_blockchain() |
||||
|> import_pools() |
||||
|
||||
if failed_list == [] do |
||||
:ok |
||||
else |
||||
{:retry, failed_list} |
||||
end |
||||
end |
||||
|
||||
def entry(pool_address) do |
||||
%{ |
||||
staking_address: pool_address, |
||||
retries_count: 0 |
||||
} |
||||
end |
||||
|
||||
defp fetch_from_blockchain(addresses) do |
||||
addresses |
||||
|> Enum.filter(&(&1.retries_count <= @max_retries)) |
||||
|> Enum.map(fn %{staking_address: staking_address} = pool -> |
||||
case PoolsReader.pool_data(staking_address) do |
||||
{:ok, data} -> |
||||
Map.merge(pool, data) |
||||
|
||||
error -> |
||||
Map.put(pool, :error, error) |
||||
end |
||||
end) |
||||
end |
||||
|
||||
defp import_pools(pools) do |
||||
{failed, success} = |
||||
Enum.reduce(pools, {[], []}, fn |
||||
%{error: _error, staking_address: address}, {failed, success} -> |
||||
{[address | failed], success} |
||||
|
||||
pool, {failed, success} -> |
||||
{failed, [changeset(pool) | success]} |
||||
end) |
||||
|
||||
import_params = %{ |
||||
staking_pools: %{params: success}, |
||||
timeout: :infinity |
||||
} |
||||
|
||||
case Chain.import(import_params) do |
||||
{:ok, _} -> |
||||
:ok |
||||
|
||||
{:error, reason} -> |
||||
Logger.debug(fn -> ["failed to import staking pools: ", inspect(reason)] end, |
||||
error_count: Enum.count(pools) |
||||
) |
||||
end |
||||
|
||||
failed |
||||
end |
||||
|
||||
defp changeset(%{staking_address: staking_address} = pool) do |
||||
{:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address]) |
||||
|
||||
data = |
||||
pool |
||||
|> Map.delete(:staking_address) |
||||
|> Map.put(:mining_address, mining_address) |
||||
|
||||
%{ |
||||
name: "anonymous", |
||||
primary: true, |
||||
address_hash: staking_address, |
||||
metadata: data |
||||
} |
||||
end |
||||
end |
@ -0,0 +1,205 @@ |
||||
defmodule Indexer.Fetcher.StakingPoolsTest do |
||||
use EthereumJSONRPC.Case |
||||
use Explorer.DataCase |
||||
|
||||
import Mox |
||||
|
||||
alias Indexer.Fetcher.StakingPools |
||||
alias Explorer.Staking.PoolsReader |
||||
alias Explorer.Chain.Address |
||||
|
||||
@moduletag :capture_log |
||||
|
||||
setup :verify_on_exit! |
||||
|
||||
describe "init/3" do |
||||
test "returns pools addresses" do |
||||
get_pools_from_blockchain(2) |
||||
|
||||
list = StakingPools.init([], &[&1 | &2], []) |
||||
|
||||
assert Enum.count(list) == 6 |
||||
end |
||||
end |
||||
|
||||
describe "run/3" do |
||||
test "one success import from pools" do |
||||
get_pools_from_blockchain(1) |
||||
|
||||
list = |
||||
PoolsReader.get_active_pools() |
||||
|> Enum.map(&StakingPools.entry/1) |
||||
|
||||
success_address = |
||||
list |
||||
|> List.first() |
||||
|> Map.get(:staking_address) |
||||
|
||||
get_pool_data_from_blockchain() |
||||
|
||||
assert {:retry, retry_list} = StakingPools.run(list, nil) |
||||
assert Enum.count(retry_list) == 2 |
||||
|
||||
pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address) |
||||
assert pool.name == "anonymous" |
||||
end |
||||
end |
||||
|
||||
defp get_pools_from_blockchain(n) do |
||||
expect( |
||||
EthereumJSONRPC.Mox, |
||||
:json_rpc, |
||||
n, |
||||
fn [%{id: id, method: "eth_call", params: _}], _options -> |
||||
{:ok, |
||||
[ |
||||
%{ |
||||
id: id, |
||||
jsonrpc: "2.0", |
||||
result: |
||||
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba" |
||||
} |
||||
]} |
||||
end |
||||
) |
||||
end |
||||
|
||||
defp get_pool_data_from_blockchain() do |
||||
expect( |
||||
EthereumJSONRPC.Mox, |
||||
:json_rpc, |
||||
4, |
||||
fn requests, _opts -> |
||||
{:ok, |
||||
Enum.map(requests, fn |
||||
# miningByStakingAddress |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" |
||||
} |
||||
|
||||
# isPoolActive |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
||||
} |
||||
|
||||
# poolDelegators |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: |
||||
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# stakeAmountTotalMinusOrderedWithdraw |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# isValidator |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
||||
} |
||||
|
||||
# validatorCounter |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000002" |
||||
} |
||||
|
||||
# isValidatorBanned |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# bannedUntil |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
|
||||
# banCounter |
||||
%{ |
||||
id: id, |
||||
method: "eth_call", |
||||
params: [ |
||||
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, |
||||
"latest" |
||||
] |
||||
} -> |
||||
%{ |
||||
id: id, |
||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
end)} |
||||
end |
||||
) |
||||
end |
||||
end |
Loading…
Reference in new issue