Consolidate POSDAO contract reading (#2371)
Previously, staking contract reading was arbitrarily spread across Explorer.Staking.ContractState, Explorer.Staking.PoolsReader and Indexer.Fetcher.StakingPools. Using async fetcher infrastructure for staking contracts is inadequate, as blocks may arrive out of order, while we want fetching to be triggered by newer block arrival. Also, contract calls were not batched enough, and it was hard to follow their sequence across involved modules. Now, on each incoming block, which is newer than the last seen, we fully update not only global contract state, but all validators, pools and delegators. All requests are intelligently compiled into four batches. These are defined together to give better overview of performed actions. Chain.import infrastructure is still used for inserting data into DB to leverage smart batching of INSERT queries. Likelihood calculation is added: it zips results of getPoolsToBeElected and getPoolsLikelihood calls to make a map of addresses. Only POS_STAKING_CONTRACT env variable is required now. Other contract addresses are fetched from it during launch. ABIs are concatenated together during launch as well, as we don't support batch requests against distinct ABIs yet. This should be addressed in further PRs. Up-to-date ABIs copied from build artifacts of posdao-contracts repo. File names now correspond to contract names, README directs to their origin. * Fetch inactive delegators, mark pools and delegators deleted (#2205) * Fetch min stakes and token contract address from StakingAuRa (#2313) * Fetch block reward ratio using validatorRewardPercent getter (#2424) * Add missing `banned_until` field in ON CONFLICT clause for staking pools.staking
parent
30109ca4bc
commit
6a5916764d
@ -0,0 +1,100 @@ |
|||||||
|
defmodule Explorer.Staking.ContractReader do |
||||||
|
@moduledoc """ |
||||||
|
Routines for batched fetching of information from POSDAO contracts |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.SmartContract.Reader |
||||||
|
|
||||||
|
def global_requests do |
||||||
|
[ |
||||||
|
token_contract_address: {:staking, "erc20TokenContract", []}, |
||||||
|
min_candidate_stake: {:staking, "candidateMinStake", []}, |
||||||
|
min_delegator_stake: {:staking, "delegatorMinStake", []}, |
||||||
|
epoch_number: {:staking, "stakingEpoch", []}, |
||||||
|
epoch_end_block: {:staking, "stakingEpochEndBlock", []}, |
||||||
|
active_pools: {:staking, "getPools", []}, |
||||||
|
inactive_pools: {:staking, "getPoolsInactive", []}, |
||||||
|
pools_likely: {:staking, "getPoolsToBeElected", []}, |
||||||
|
pools_likelihood: {:staking, "getPoolsLikelihood", []}, |
||||||
|
validators: {:validator_set, "getValidators", []} |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def pool_staking_requests(staking_address) do |
||||||
|
[ |
||||||
|
mining_address_hash: {:validator_set, "miningByStakingAddress", [staking_address]}, |
||||||
|
is_active: {:staking, "isPoolActive", [staking_address]}, |
||||||
|
active_delegators: {:staking, "poolDelegators", [staking_address]}, |
||||||
|
inactive_delegators: {:staking, "poolDelegatorsInactive", [staking_address]}, |
||||||
|
staked_amount: {:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]}, |
||||||
|
self_staked_amount: {:staking, "stakeAmountMinusOrderedWithdraw", [staking_address, staking_address]}, |
||||||
|
block_reward: {:block_reward, "validatorRewardPercent", [staking_address]} |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def pool_mining_requests(mining_address) do |
||||||
|
[ |
||||||
|
is_validator: {:validator_set, "isValidator", [mining_address]}, |
||||||
|
was_validator_count: {:validator_set, "validatorCounter", [mining_address]}, |
||||||
|
is_banned: {:validator_set, "isValidatorBanned", [mining_address]}, |
||||||
|
banned_until: {:validator_set, "bannedUntil", [mining_address]}, |
||||||
|
was_banned_count: {:validator_set, "banCounter", [mining_address]} |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def delegator_requests(pool_address, delegator_address) do |
||||||
|
[ |
||||||
|
stake_amount: {:staking, "stakeAmount", [pool_address, delegator_address]}, |
||||||
|
ordered_withdraw: {:staking, "orderedWithdrawAmount", [pool_address, delegator_address]}, |
||||||
|
max_withdraw_allowed: {:staking, "maxWithdrawAllowed", [pool_address, delegator_address]}, |
||||||
|
max_ordered_withdraw_allowed: {:staking, "maxWithdrawOrderAllowed", [pool_address, delegator_address]}, |
||||||
|
ordered_withdraw_epoch: {:staking, "orderWithdrawEpoch", [pool_address, delegator_address]} |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def perform_requests(requests, contracts, abi) do |
||||||
|
requests |
||||||
|
|> generate_requests(contracts) |
||||||
|
|> Reader.query_contracts(abi) |
||||||
|
|> parse_responses(requests) |
||||||
|
end |
||||||
|
|
||||||
|
def perform_grouped_requests(requests, keys, contracts, abi) do |
||||||
|
requests |
||||||
|
|> List.flatten() |
||||||
|
|> generate_requests(contracts) |
||||||
|
|> Reader.query_contracts(abi) |
||||||
|
|> parse_grouped_responses(keys, requests) |
||||||
|
end |
||||||
|
|
||||||
|
defp generate_requests(functions, contracts) do |
||||||
|
Enum.map(functions, fn {_, {contract, function, args}} -> |
||||||
|
%{ |
||||||
|
contract_address: contracts[contract], |
||||||
|
function_name: function, |
||||||
|
args: args |
||||||
|
} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_responses(responses, requests) do |
||||||
|
requests |
||||||
|
|> Enum.zip(responses) |
||||||
|
|> Enum.into(%{}, fn {{key, _}, {:ok, response}} -> |
||||||
|
case response do |
||||||
|
[item] -> {key, item} |
||||||
|
items -> {key, items} |
||||||
|
end |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_grouped_responses(responses, keys, grouped_requests) do |
||||||
|
{grouped_responses, _} = Enum.map_reduce(grouped_requests, responses, &Enum.split(&2, length(&1))) |
||||||
|
|
||||||
|
[keys, grouped_requests, grouped_responses] |
||||||
|
|> Enum.zip() |
||||||
|
|> Enum.into(%{}, fn {key, requests, responses} -> |
||||||
|
{key, parse_responses(responses, requests)} |
||||||
|
end) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,205 @@ |
|||||||
|
defmodule Explorer.Staking.ContractState do |
||||||
|
@moduledoc """ |
||||||
|
Fetches all information from POSDAO staking contracts. |
||||||
|
All contract calls are batched into four requests, according to their dependencies. |
||||||
|
Subscribes to new block notifications and refreshes when previously unseen block arrives. |
||||||
|
""" |
||||||
|
|
||||||
|
use GenServer |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.Events.Subscriber |
||||||
|
alias Explorer.SmartContract.Reader |
||||||
|
alias Explorer.Staking.ContractReader |
||||||
|
|
||||||
|
@table_name __MODULE__ |
||||||
|
@table_keys [ |
||||||
|
:token_contract_address, |
||||||
|
:min_candidate_stake, |
||||||
|
:min_delegator_stake, |
||||||
|
:epoch_number, |
||||||
|
:epoch_end_block |
||||||
|
] |
||||||
|
|
||||||
|
defstruct [ |
||||||
|
:seen_block, |
||||||
|
:contracts, |
||||||
|
:abi |
||||||
|
] |
||||||
|
|
||||||
|
@spec get(atom(), value) :: value when value: any() |
||||||
|
def get(key, default \\ nil) when key in @table_keys do |
||||||
|
with info when info != :undefined <- :ets.info(@table_name), |
||||||
|
[{_, value}] <- :ets.lookup(@table_name, key) do |
||||||
|
value |
||||||
|
else |
||||||
|
_ -> default |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def start_link([]) do |
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
def init([]) do |
||||||
|
:ets.new(@table_name, [ |
||||||
|
:set, |
||||||
|
:named_table, |
||||||
|
:public, |
||||||
|
read_concurrency: true, |
||||||
|
write_concurrency: true |
||||||
|
]) |
||||||
|
|
||||||
|
Subscriber.to(:blocks, :realtime) |
||||||
|
|
||||||
|
staking_abi = abi("StakingAuRa") |
||||||
|
validator_set_abi = abi("ValidatorSetAuRa") |
||||||
|
block_reward_abi = abi("BlockRewardAuRa") |
||||||
|
|
||||||
|
staking_contract_address = Application.get_env(:explorer, __MODULE__)[:staking_contract_address] |
||||||
|
|
||||||
|
%{"validatorSetContract" => {:ok, [validator_set_contract_address]}} = |
||||||
|
Reader.query_contract(staking_contract_address, staking_abi, %{"validatorSetContract" => []}) |
||||||
|
|
||||||
|
%{"blockRewardContract" => {:ok, [block_reward_contract_address]}} = |
||||||
|
Reader.query_contract(validator_set_contract_address, validator_set_abi, %{"blockRewardContract" => []}) |
||||||
|
|
||||||
|
state = %__MODULE__{ |
||||||
|
seen_block: 0, |
||||||
|
contracts: %{ |
||||||
|
staking: staking_contract_address, |
||||||
|
validator_set: validator_set_contract_address, |
||||||
|
block_reward: block_reward_contract_address |
||||||
|
}, |
||||||
|
abi: staking_abi ++ validator_set_abi ++ block_reward_abi |
||||||
|
} |
||||||
|
|
||||||
|
{:ok, state, {:continue, []}} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_continue(_, state) do |
||||||
|
fetch_state(state.contracts, state.abi) |
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@doc "Handles new blocks and decides to fetch fresh chain info" |
||||||
|
def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do |
||||||
|
latest_block = Enum.max_by(blocks, & &1.number) |
||||||
|
|
||||||
|
if latest_block.number > state.seen_block do |
||||||
|
fetch_state(state.contracts, state.abi) |
||||||
|
{:noreply, %{state | seen_block: latest_block.number}} |
||||||
|
else |
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp fetch_state(contracts, abi) do |
||||||
|
global_responses = ContractReader.perform_requests(ContractReader.global_requests(), contracts, abi) |
||||||
|
|
||||||
|
settings = |
||||||
|
global_responses |
||||||
|
|> Map.take([ |
||||||
|
:token_contract_address, |
||||||
|
:min_candidate_stake, |
||||||
|
:min_delegator_stake, |
||||||
|
:epoch_number, |
||||||
|
:epoch_end_block |
||||||
|
]) |
||||||
|
|> Map.to_list() |
||||||
|
|
||||||
|
:ets.insert(@table_name, settings) |
||||||
|
|
||||||
|
pools = global_responses.active_pools ++ global_responses.inactive_pools |
||||||
|
|
||||||
|
pool_staking_responses = |
||||||
|
pools |
||||||
|
|> Enum.map(&ContractReader.pool_staking_requests/1) |
||||||
|
|> ContractReader.perform_grouped_requests(pools, contracts, abi) |
||||||
|
|
||||||
|
pool_mining_responses = |
||||||
|
pools |
||||||
|
|> Enum.map(&ContractReader.pool_mining_requests(pool_staking_responses[&1].mining_address_hash)) |
||||||
|
|> ContractReader.perform_grouped_requests(pools, contracts, abi) |
||||||
|
|
||||||
|
delegators = |
||||||
|
Enum.flat_map(pool_staking_responses, fn {pool_address, responses} -> |
||||||
|
Enum.map(responses.active_delegators, &{pool_address, &1, true}) ++ |
||||||
|
Enum.map(responses.inactive_delegators, &{pool_address, &1, false}) |
||||||
|
end) |
||||||
|
|
||||||
|
delegator_responses = |
||||||
|
delegators |
||||||
|
|> Enum.map(fn {pool_address, delegator_address, _} -> |
||||||
|
ContractReader.delegator_requests(pool_address, delegator_address) |
||||||
|
end) |
||||||
|
|> ContractReader.perform_grouped_requests(delegators, contracts, abi) |
||||||
|
|
||||||
|
staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.staked_amount) |
||||||
|
[likelihood_values, total_likelihood] = global_responses.pools_likelihood |
||||||
|
|
||||||
|
likelihood = |
||||||
|
global_responses.pools_likely |
||||||
|
|> Enum.zip(likelihood_values) |
||||||
|
|> Enum.into(%{}) |
||||||
|
|
||||||
|
pool_entries = |
||||||
|
Enum.map(pools, fn staking_address -> |
||||||
|
staking_response = pool_staking_responses[staking_address] |
||||||
|
mining_response = pool_mining_responses[staking_address] |
||||||
|
|
||||||
|
%{ |
||||||
|
staking_address_hash: staking_address, |
||||||
|
delegators_count: length(staking_response.active_delegators), |
||||||
|
staked_ratio: ratio(staking_response.staked_amount, staked_total), |
||||||
|
likelihood: ratio(likelihood[staking_address] || 0, total_likelihood), |
||||||
|
block_reward_ratio: staking_response.block_reward / 10_000, |
||||||
|
is_deleted: false |
||||||
|
} |
||||||
|
|> Map.merge( |
||||||
|
Map.take(staking_response, [ |
||||||
|
:mining_address_hash, |
||||||
|
:is_active, |
||||||
|
:staked_amount, |
||||||
|
:self_staked_amount |
||||||
|
]) |
||||||
|
) |
||||||
|
|> Map.merge( |
||||||
|
Map.take(mining_response, [ |
||||||
|
:is_validator, |
||||||
|
:was_validator_count, |
||||||
|
:is_banned, |
||||||
|
:banned_until, |
||||||
|
:was_banned_count |
||||||
|
]) |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
delegator_entries = |
||||||
|
Enum.map(delegator_responses, fn {{pool_address, delegator_address, is_active}, response} -> |
||||||
|
Map.merge(response, %{ |
||||||
|
delegator_address_hash: delegator_address, |
||||||
|
pool_address_hash: pool_address, |
||||||
|
is_active: is_active |
||||||
|
}) |
||||||
|
end) |
||||||
|
|
||||||
|
{:ok, _} = |
||||||
|
Chain.import(%{ |
||||||
|
staking_pools: %{params: pool_entries}, |
||||||
|
staking_pools_delegators: %{params: delegator_entries}, |
||||||
|
timeout: :infinity |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
defp ratio(_numerator, 0), do: 0 |
||||||
|
defp ratio(numerator, denominator), do: numerator / denominator * 100 |
||||||
|
|
||||||
|
# sobelow_skip ["Traversal"] |
||||||
|
defp abi(file_name) do |
||||||
|
:explorer |
||||||
|
|> Application.app_dir("priv/contracts_abi/posdao/#{file_name}.json") |
||||||
|
|> File.read!() |
||||||
|
|> Jason.decode!() |
||||||
|
end |
||||||
|
end |
@ -1,126 +0,0 @@ |
|||||||
defmodule Explorer.Staking.EpochCounter do |
|
||||||
@moduledoc """ |
|
||||||
Fetches current staking epoch number and the epoch end block number. |
|
||||||
It subscribes to handle new blocks and conclude whether the epoch is over. |
|
||||||
""" |
|
||||||
|
|
||||||
use GenServer |
|
||||||
|
|
||||||
alias Explorer.Chain.Events.Subscriber |
|
||||||
alias Explorer.SmartContract.Reader |
|
||||||
|
|
||||||
@table_name __MODULE__ |
|
||||||
@epoch_key "epoch_num" |
|
||||||
@epoch_end_key "epoch_end_block" |
|
||||||
|
|
||||||
@doc "Current staking epoch number" |
|
||||||
def epoch_number do |
|
||||||
if :ets.info(@table_name) != :undefined do |
|
||||||
case :ets.lookup(@table_name, @epoch_key) do |
|
||||||
[{_, epoch_num}] -> |
|
||||||
epoch_num |
|
||||||
|
|
||||||
_ -> |
|
||||||
0 |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
@doc "Block number on which will start new epoch" |
|
||||||
def epoch_end_block do |
|
||||||
if :ets.info(@table_name) != :undefined do |
|
||||||
case :ets.lookup(@table_name, @epoch_end_key) do |
|
||||||
[{_, epoch_end}] -> |
|
||||||
epoch_end |
|
||||||
|
|
||||||
_ -> |
|
||||||
0 |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
def start_link([]) do |
|
||||||
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
|
||||||
end |
|
||||||
|
|
||||||
def init([]) do |
|
||||||
:ets.new(@table_name, [ |
|
||||||
:set, |
|
||||||
:named_table, |
|
||||||
:public, |
|
||||||
write_concurrency: true |
|
||||||
]) |
|
||||||
|
|
||||||
Subscriber.to(:blocks, :realtime) |
|
||||||
{:ok, [], {:continue, :epoch_info}} |
|
||||||
end |
|
||||||
|
|
||||||
def handle_continue(:epoch_info, state) do |
|
||||||
fetch_epoch_info() |
|
||||||
{:noreply, state} |
|
||||||
end |
|
||||||
|
|
||||||
@doc "Handles new blocks and decides to fetch new epoch info" |
|
||||||
def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do |
|
||||||
new_block_number = |
|
||||||
blocks |
|
||||||
|> Enum.map(&Map.get(&1, :number, 0)) |
|
||||||
|> Enum.max(fn -> 0 end) |
|
||||||
|
|
||||||
case :ets.lookup(@table_name, @epoch_end_key) do |
|
||||||
[] -> |
|
||||||
fetch_epoch_info() |
|
||||||
|
|
||||||
[{_, epoch_end_block}] when epoch_end_block < new_block_number -> |
|
||||||
fetch_epoch_info() |
|
||||||
|
|
||||||
_ -> |
|
||||||
:ok |
|
||||||
end |
|
||||||
|
|
||||||
{:noreply, state} |
|
||||||
end |
|
||||||
|
|
||||||
defp fetch_epoch_info do |
|
||||||
# 794c0c68 = keccak256(stakingEpoch()) |
|
||||||
# 8c2243ae = keccak256(stakingEpochEndBlock()) |
|
||||||
with data <- get_epoch_info(), |
|
||||||
{:ok, [epoch_num]} <- data["794c0c68"], |
|
||||||
{:ok, [epoch_end_block]} <- data["8c2243ae"] do |
|
||||||
:ets.insert(@table_name, {@epoch_key, epoch_num}) |
|
||||||
:ets.insert(@table_name, {@epoch_end_key, epoch_end_block}) |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
defp get_epoch_info do |
|
||||||
contract_abi = abi("staking.json") |
|
||||||
|
|
||||||
method_ids = ["794c0c68", "8c2243ae"] |
|
||||||
|
|
||||||
method_ids |
|
||||||
|> Enum.map(fn method_id -> |
|
||||||
%{ |
|
||||||
contract_address: staking_address(), |
|
||||||
method_id: method_id, |
|
||||||
args: [] |
|
||||||
} |
|
||||||
end) |
|
||||||
|> Reader.query_contracts(contract_abi) |
|
||||||
|> Enum.zip(method_ids) |
|
||||||
|> Enum.into(%{}, fn {response, method_id} -> |
|
||||||
{method_id, response} |
|
||||||
end) |
|
||||||
end |
|
||||||
|
|
||||||
defp staking_address do |
|
||||||
Application.get_env(:explorer, __MODULE__, [])[:staking_contract_address] |
|
||||||
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 |
|
@ -1,174 +0,0 @@ |
|||||||
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 |
|
||||||
# 673a2a1f = keccak256(getPools()) |
|
||||||
{:ok, [active_pools]} = call_staking_method("673a2a1f", []) |
|
||||||
active_pools |
|
||||||
end |
|
||||||
|
|
||||||
@spec get_inactive_pools() :: [String.t()] |
|
||||||
def get_inactive_pools do |
|
||||||
# df6f55f5 = keccak256(getPoolsInactive()) |
|
||||||
{:ok, [inactive_pools]} = call_staking_method("df6f55f5", []) |
|
||||||
inactive_pools |
|
||||||
end |
|
||||||
|
|
||||||
@spec pool_data(String.t()) :: {:ok, map()} | :error |
|
||||||
def pool_data(staking_address) do |
|
||||||
# 00535175 = keccak256(miningByStakingAddress(address)) |
|
||||||
with {:ok, [mining_address]} <- call_validators_method("00535175", [staking_address]), |
|
||||||
data = fetch_pool_data(staking_address, mining_address), |
|
||||||
{:ok, [is_active]} <- data["a711e6a1"], |
|
||||||
{:ok, [delegator_addresses]} <- data["9ea8082b"], |
|
||||||
delegators_count = Enum.count(delegator_addresses), |
|
||||||
delegators = delegators_data(delegator_addresses, staking_address), |
|
||||||
{:ok, [staked_amount]} <- data["234fbf2b"], |
|
||||||
{:ok, [self_staked_amount]} <- data["58daab6a"], |
|
||||||
{:ok, [is_validator]} <- data["facd743b"], |
|
||||||
{:ok, [was_validator_count]} <- data["b41832e4"], |
|
||||||
{:ok, [is_banned]} <- data["a92252ae"], |
|
||||||
{:ok, [banned_until]} <- data["5836d08a"], |
|
||||||
{:ok, [was_banned_count]} <- data["1d0cd4c6"] do |
|
||||||
{ |
|
||||||
:ok, |
|
||||||
%{ |
|
||||||
staking_address_hash: staking_address, |
|
||||||
mining_address_hash: mining_address, |
|
||||||
is_active: is_active, |
|
||||||
delegators_count: delegators_count, |
|
||||||
staked_amount: staked_amount, |
|
||||||
self_staked_amount: self_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, |
|
||||||
delegators: delegators |
|
||||||
} |
|
||||||
} |
|
||||||
else |
|
||||||
_ -> |
|
||||||
:error |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
defp delegators_data(delegators, pool_address) do |
|
||||||
Enum.map(delegators, fn address -> |
|
||||||
# a697ecff = keccak256(stakeAmount(address,address)) |
|
||||||
# e9ab0300 = keccak256(orderedWithdrawAmount(address,address)) |
|
||||||
# 6bda1577 = keccak256(maxWithdrawAllowed(address,address)) |
|
||||||
# 950a6513 = keccak256(maxWithdrawOrderAllowed(address,address)) |
|
||||||
# a4205967 = keccak256(orderWithdrawEpoch(address,address)) |
|
||||||
data = |
|
||||||
call_methods([ |
|
||||||
{:staking, "a697ecff", [pool_address, address]}, |
|
||||||
{:staking, "e9ab0300", [pool_address, address]}, |
|
||||||
{:staking, "6bda1577", [pool_address, address]}, |
|
||||||
{:staking, "950a6513", [pool_address, address]}, |
|
||||||
{:staking, "a4205967", [pool_address, address]} |
|
||||||
]) |
|
||||||
|
|
||||||
{:ok, [stake_amount]} = data["a697ecff"] |
|
||||||
{:ok, [ordered_withdraw]} = data["e9ab0300"] |
|
||||||
{:ok, [max_withdraw_allowed]} = data["6bda1577"] |
|
||||||
{:ok, [max_ordered_withdraw_allowed]} = data["950a6513"] |
|
||||||
{:ok, [ordered_withdraw_epoch]} = data["a4205967"] |
|
||||||
|
|
||||||
%{ |
|
||||||
delegator_address_hash: address, |
|
||||||
pool_address_hash: pool_address, |
|
||||||
stake_amount: stake_amount, |
|
||||||
ordered_withdraw: ordered_withdraw, |
|
||||||
max_withdraw_allowed: max_withdraw_allowed, |
|
||||||
max_ordered_withdraw_allowed: max_ordered_withdraw_allowed, |
|
||||||
ordered_withdraw_epoch: ordered_withdraw_epoch |
|
||||||
} |
|
||||||
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_pool_data(staking_address, mining_address) do |
|
||||||
# a711e6a1 = keccak256(isPoolActive(address)) |
|
||||||
# 9ea8082b = keccak256(poolDelegators(address)) |
|
||||||
# 234fbf2b = keccak256(stakeAmountTotalMinusOrderedWithdraw(address)) |
|
||||||
# 58daab6a = keccak256(stakeAmountMinusOrderedWithdraw(address,address)) |
|
||||||
# facd743b = keccak256(isValidator(address)) |
|
||||||
# b41832e4 = keccak256(validatorCounter(address)) |
|
||||||
# a92252ae = keccak256(isValidatorBanned(address)) |
|
||||||
# 5836d08a = keccak256(bannedUntil(address)) |
|
||||||
# 1d0cd4c6 = keccak256(banCounter(address)) |
|
||||||
call_methods([ |
|
||||||
{:staking, "a711e6a1", [staking_address]}, |
|
||||||
{:staking, "9ea8082b", [staking_address]}, |
|
||||||
{:staking, "234fbf2b", [staking_address]}, |
|
||||||
{:staking, "58daab6a", [staking_address, staking_address]}, |
|
||||||
{:validators, "facd743b", [mining_address]}, |
|
||||||
{:validators, "b41832e4", [mining_address]}, |
|
||||||
{:validators, "a92252ae", [mining_address]}, |
|
||||||
{:validators, "5836d08a", [mining_address]}, |
|
||||||
{:validators, "1d0cd4c6", [mining_address]} |
|
||||||
]) |
|
||||||
end |
|
||||||
|
|
||||||
defp call_methods(methods) do |
|
||||||
contract_abi = abi("staking.json") ++ abi("validators.json") |
|
||||||
|
|
||||||
methods |
|
||||||
|> Enum.map(&format_request/1) |
|
||||||
|> Reader.query_contracts(contract_abi) |
|
||||||
|> Enum.zip(methods) |
|
||||||
|> Enum.into(%{}, fn {response, {_, method_id, _}} -> |
|
||||||
{method_id, response} |
|
||||||
end) |
|
||||||
end |
|
||||||
|
|
||||||
defp format_request({contract_name, method_id, params}) do |
|
||||||
%{ |
|
||||||
contract_address: contract(contract_name), |
|
||||||
method_id: method_id, |
|
||||||
args: params |
|
||||||
} |
|
||||||
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,632 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "DELEGATORS_ALIQUOT", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "bridgeTokenFee", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "isRewarding", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "bool" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "mintedForAccountInBlock", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "epochPoolNativeReward", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "mintedForAccount", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "mintedInBlock", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "mintedTotally", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "tokenRewardUndistributed", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "nativeRewardUndistributed", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "isSnapshotting", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "bool" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "mintedTotallyByBridge", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "epochPoolTokenReward", |
||||||
|
"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" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "bridgeNativeFee", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "snapshotTotalStakeAmount", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "blocksCreated", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"anonymous": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"indexed": false, |
||||||
|
"name": "amount", |
||||||
|
"type": "uint256" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"indexed": true, |
||||||
|
"name": "receiver", |
||||||
|
"type": "address" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"indexed": true, |
||||||
|
"name": "bridge", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "AddedReceiver", |
||||||
|
"type": "event" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"anonymous": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"indexed": false, |
||||||
|
"name": "receivers", |
||||||
|
"type": "address[]" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"indexed": false, |
||||||
|
"name": "rewards", |
||||||
|
"type": "uint256[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "MintedNative", |
||||||
|
"type": "event" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_amount", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "addBridgeNativeFeeReceivers", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_amount", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "addBridgeTokenFeeReceivers", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_amount", |
||||||
|
"type": "uint256" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "_receiver", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "addExtraReceiver", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_validatorSet", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "initialize", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_bridge", |
||||||
|
"type": "address" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "_prevBlockRewardContract", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "migrateMintingStatistics", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "benefactors", |
||||||
|
"type": "address[]" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "kind", |
||||||
|
"type": "uint16[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "reward", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "receiversNative", |
||||||
|
"type": "address[]" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "rewardsNative", |
||||||
|
"type": "uint256[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_bridgesAllowed", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "setErcToNativeBridgesAllowed", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_bridgesAllowed", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "setNativeToErcBridgesAllowed", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": false, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_bridgesAllowed", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "setErcToErcBridgesAllowed", |
||||||
|
"outputs": [], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "nonpayable", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "blockRewardContractId", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "bytes4" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "pure", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "ercToErcBridgesAllowed", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "ercToNativeBridgesAllowed", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "extraReceiversQueueSize", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "isInitialized", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "bool" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "nativeToErcBridgesAllowed", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_validatorStakingAddress", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "snapshotRewardPercents", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "result", |
||||||
|
"type": "uint256[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_validatorStakingAddress", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "snapshotStakers", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "result", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [], |
||||||
|
"name": "snapshotStakingAddresses", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "address[]" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"constant": true, |
||||||
|
"inputs": [ |
||||||
|
{ |
||||||
|
"name": "_stakingAddress", |
||||||
|
"type": "address" |
||||||
|
} |
||||||
|
], |
||||||
|
"name": "validatorRewardPercent", |
||||||
|
"outputs": [ |
||||||
|
{ |
||||||
|
"name": "", |
||||||
|
"type": "uint256" |
||||||
|
} |
||||||
|
], |
||||||
|
"payable": false, |
||||||
|
"stateMutability": "view", |
||||||
|
"type": "function" |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,2 @@ |
|||||||
|
ABIs are taken from compiled contract JSONs in the `build/` directory of https://github.com/poanetwork/posdao-contracts. |
||||||
|
Docs: https://poanetwork.github.io/posdao-contracts/docs/ |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.DelegatorsDeletedColum do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:staking_pools_delegators) do |
||||||
|
add(:is_active, :boolean, default: true) |
||||||
|
add(:is_deleted, :boolean, default: false) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,9 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.AddBlockRewardToPools do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:staking_pools) do |
||||||
|
add(:block_reward_ratio, :decimal, precision: 5, scale: 2) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,208 @@ |
|||||||
|
defmodule Explorer.Staking.ContractStateTest do |
||||||
|
use EthereumJSONRPC.Case |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
import Mox |
||||||
|
|
||||||
|
alias Explorer.Chain.{StakingPool, StakingPoolsDelegator} |
||||||
|
alias Explorer.Chain.Events.Publisher |
||||||
|
alias Explorer.Repo |
||||||
|
alias Explorer.Staking.ContractState |
||||||
|
|
||||||
|
setup :verify_on_exit! |
||||||
|
setup :set_mox_global |
||||||
|
|
||||||
|
test "when disabled, returns default values" do |
||||||
|
assert ContractState.get(:epoch_number, 0) == 0 |
||||||
|
assert ContractState.get(:epoch_end_block, 0) == 0 |
||||||
|
assert ContractState.get(:min_delegator_stake, 1) == 1 |
||||||
|
assert ContractState.get(:min_candidate_stake, 1) == 1 |
||||||
|
assert ContractState.get(:token_contract_address) == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "fetch new epoch data" do |
||||||
|
set_init_mox() |
||||||
|
set_mox() |
||||||
|
|
||||||
|
Application.put_env(:explorer, ContractState, |
||||||
|
enabled: true, |
||||||
|
staking_contract_address: "0x1100000000000000000000000000000000000001" |
||||||
|
) |
||||||
|
|
||||||
|
start_supervised!(ContractState) |
||||||
|
|
||||||
|
set_mox() |
||||||
|
Publisher.broadcast([{:blocks, [%Explorer.Chain.Block{number: 6000}]}], :realtime) |
||||||
|
Publisher.broadcast([{:blocks, [%Explorer.Chain.Block{number: 5999}]}], :realtime) |
||||||
|
Publisher.broadcast([{:blocks, [%Explorer.Chain.Block{number: 6000}]}], :realtime) |
||||||
|
|
||||||
|
set_mox() |
||||||
|
Publisher.broadcast([{:blocks, [%Explorer.Chain.Block{number: 6001}]}], :realtime) |
||||||
|
|
||||||
|
Process.sleep(500) |
||||||
|
|
||||||
|
assert ContractState.get(:epoch_number) == 74 |
||||||
|
assert ContractState.get(:epoch_end_block) == 6000 |
||||||
|
assert ContractState.get(:min_delegator_stake) == 1_000_000_000_000_000_000 |
||||||
|
assert ContractState.get(:min_candidate_stake) == 1_000_000_000_000_000_000 |
||||||
|
assert ContractState.get(:token_contract_address) == "0x6f7a73c96bd56f8b0debc795511eda135e105ea3" |
||||||
|
|
||||||
|
assert Repo.aggregate(StakingPool, :count, :id) == 4 |
||||||
|
assert Repo.aggregate(StakingPoolsDelegator, :count, :id) == 3 |
||||||
|
end |
||||||
|
|
||||||
|
defp set_init_mox() do |
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 1 |
||||||
|
{:ok, format_responses(["0x0000000000000000000000001000000000000000000000000000000000000001"])} |
||||||
|
end |
||||||
|
) |
||||||
|
|
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 1 |
||||||
|
{:ok, format_responses(["0x0000000000000000000000002000000000000000000000000000000000000001"])} |
||||||
|
end |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
defp set_mox() do |
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 10 |
||||||
|
|
||||||
|
{:ok, |
||||||
|
format_responses([ |
||||||
|
"0x0000000000000000000000006f7a73c96bd56f8b0debc795511eda135e105ea3", |
||||||
|
"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000000004a", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000001770", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000b916e7e1f4bcb13549602ed042d36746fd0d96c9000000000000000000000000db9cb2478d917719c53862008672166808258577", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b916e7e1f4bcb13549602ed042d36746fd0d96c9000000000000000000000000db9cb2478d917719c53862008672166808258577", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000514000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000044c", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78000000000000000000000000f67cc5231c5858ad6cc87b105217426e17b824bb000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3" |
||||||
|
])} |
||||||
|
end |
||||||
|
) |
||||||
|
|
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 28 |
||||||
|
|
||||||
|
{:ok, |
||||||
|
format_responses([ |
||||||
|
"0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000003d090", |
||||||
|
"0x000000000000000000000000f67cc5231c5858ad6cc87b105217426e17b824bb", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000001bc16d674ec80000", |
||||||
|
"0x0000000000000000000000000000000000000000000000001bc16d674ec80000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000051615", |
||||||
|
"0x000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000009d99f80d3b59cca783f11918311fb31212fb7500000000000000000000000008d6867958e1cab5c39160a1d30fbc68ac55b45ef", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x00000000000000000000000000000000000000000000000098a7d9b8314c0000", |
||||||
|
"0x0000000000000000000000000000000000000000000000001bc16d674ec80000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000051615", |
||||||
|
"0x000000000000000000000000720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e4978fac7adfc925352dbc7e1962e6545142eeee", |
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x00000000000000000000000000000000000000000000000029a2241af62c0000", |
||||||
|
"0x0000000000000000000000000000000000000000000000001bc16d674ec80000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000051615" |
||||||
|
])} |
||||||
|
end |
||||||
|
) |
||||||
|
|
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 20 |
||||||
|
|
||||||
|
{:ok, |
||||||
|
format_responses([ |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000000004b", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000002", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000000004a", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000001", |
||||||
|
"0x000000000000000000000000000000000000000000000000000000000000004a", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||||
|
])} |
||||||
|
end |
||||||
|
) |
||||||
|
|
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn requests, _opts -> |
||||||
|
assert length(requests) == 15 |
||||||
|
|
||||||
|
{:ok, |
||||||
|
format_responses([ |
||||||
|
"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||||
|
])} |
||||||
|
end |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
defp format_responses(responses) do |
||||||
|
responses |
||||||
|
|> Enum.with_index() |
||||||
|
|> Enum.map(fn {response, index} -> |
||||||
|
%{ |
||||||
|
id: index, |
||||||
|
jsonrpc: "2.0", |
||||||
|
result: response |
||||||
|
} |
||||||
|
end) |
||||||
|
end |
||||||
|
end |
@ -1,97 +0,0 @@ |
|||||||
defmodule Explorer.Staking.EpochCounterTest do |
|
||||||
use ExUnit.Case, async: false |
|
||||||
|
|
||||||
import Mox |
|
||||||
|
|
||||||
alias Explorer.Staking.EpochCounter |
|
||||||
alias Explorer.Chain.Events.Publisher |
|
||||||
|
|
||||||
setup :verify_on_exit! |
|
||||||
setup :set_mox_global |
|
||||||
|
|
||||||
test "when disabled, it returns nil" do |
|
||||||
assert EpochCounter.epoch_number() == nil |
|
||||||
assert EpochCounter.epoch_end_block() == nil |
|
||||||
end |
|
||||||
|
|
||||||
test "fetch epoch data" do |
|
||||||
set_mox(10, 880) |
|
||||||
Application.put_env(:explorer, EpochCounter, enabled: true) |
|
||||||
start_supervised!(EpochCounter) |
|
||||||
|
|
||||||
Process.sleep(1_000) |
|
||||||
|
|
||||||
assert EpochCounter.epoch_number() == 10 |
|
||||||
assert EpochCounter.epoch_end_block() == 880 |
|
||||||
end |
|
||||||
|
|
||||||
test "fetch new epoch data" do |
|
||||||
set_mox(10, 880) |
|
||||||
Application.put_env(:explorer, EpochCounter, enabled: true) |
|
||||||
start_supervised!(EpochCounter) |
|
||||||
|
|
||||||
Process.sleep(1_000) |
|
||||||
|
|
||||||
assert EpochCounter.epoch_number() == 10 |
|
||||||
assert EpochCounter.epoch_end_block() == 880 |
|
||||||
|
|
||||||
event_type = :blocks |
|
||||||
broadcast_type = :realtime |
|
||||||
event_data = [%Explorer.Chain.Block{number: 881}] |
|
||||||
|
|
||||||
set_mox(11, 960) |
|
||||||
Publisher.broadcast([{event_type, event_data}], broadcast_type) |
|
||||||
|
|
||||||
Process.sleep(1_000) |
|
||||||
|
|
||||||
assert EpochCounter.epoch_number() == 11 |
|
||||||
assert EpochCounter.epoch_end_block() == 960 |
|
||||||
end |
|
||||||
|
|
||||||
defp set_mox(epoch_num, end_block_num) do |
|
||||||
expect( |
|
||||||
EthereumJSONRPC.Mox, |
|
||||||
:json_rpc, |
|
||||||
fn [ |
|
||||||
%{ |
|
||||||
id: 0, |
|
||||||
jsonrpc: "2.0", |
|
||||||
method: "eth_call", |
|
||||||
params: _ |
|
||||||
}, |
|
||||||
%{ |
|
||||||
id: 1, |
|
||||||
jsonrpc: "2.0", |
|
||||||
method: "eth_call", |
|
||||||
params: _ |
|
||||||
} |
|
||||||
], |
|
||||||
_options -> |
|
||||||
{:ok, |
|
||||||
[ |
|
||||||
%{ |
|
||||||
id: 0, |
|
||||||
jsonrpc: "2.0", |
|
||||||
result: encode_num(epoch_num) |
|
||||||
}, |
|
||||||
%{ |
|
||||||
id: 1, |
|
||||||
jsonrpc: "2.0", |
|
||||||
result: encode_num(end_block_num) |
|
||||||
} |
|
||||||
]} |
|
||||||
end |
|
||||||
) |
|
||||||
end |
|
||||||
|
|
||||||
defp encode_num(num) do |
|
||||||
selector = %ABI.FunctionSelector{function: nil, types: [uint: 32]} |
|
||||||
|
|
||||||
encoded_num = |
|
||||||
[num] |
|
||||||
|> ABI.TypeEncoder.encode(selector) |
|
||||||
|> Base.encode16(case: :lower) |
|
||||||
|
|
||||||
"0x" <> encoded_num |
|
||||||
end |
|
||||||
end |
|
@ -1,361 +0,0 @@ |
|||||||
defmodule Explorer.Token.PoolsReaderTest do |
|
||||||
use EthereumJSONRPC.Case |
|
||||||
|
|
||||||
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 = <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>> |
|
||||||
|
|
||||||
response = |
|
||||||
{:ok, |
|
||||||
%{ |
|
||||||
banned_until: 0, |
|
||||||
is_active: true, |
|
||||||
is_banned: false, |
|
||||||
is_validator: true, |
|
||||||
was_banned_count: 0, |
|
||||||
was_validator_count: 2, |
|
||||||
delegators: [ |
|
||||||
%{ |
|
||||||
delegator_address_hash: |
|
||||||
<<243, 231, 124, 74, 245, 235, 47, 51, 175, 255, 118, 25, 216, 209, 231, 81, 215, 24, 164, 145>>, |
|
||||||
max_ordered_withdraw_allowed: 1_000_000_000_000_000_000, |
|
||||||
max_withdraw_allowed: 1_000_000_000_000_000_000, |
|
||||||
ordered_withdraw: 0, |
|
||||||
ordered_withdraw_epoch: 0, |
|
||||||
pool_address_hash: |
|
||||||
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>, |
|
||||||
stake_amount: 1_000_000_000_000_000_000 |
|
||||||
} |
|
||||||
], |
|
||||||
delegators_count: 1, |
|
||||||
mining_address_hash: |
|
||||||
<<190, 105, 235, 9, 104, 34, 106, 24, 8, 151, 94, 26, 31, 33, 39, 102, 127, 43, 255, 179>>, |
|
||||||
self_staked_amount: 2_000_000_000_000_000_000, |
|
||||||
staked_amount: 3_000_000_000_000_000_000, |
|
||||||
staking_address_hash: |
|
||||||
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>> |
|
||||||
}} |
|
||||||
|
|
||||||
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, |
|
||||||
3, |
|
||||||
fn requests, _opts -> |
|
||||||
{:ok, |
|
||||||
Enum.map(requests, fn |
|
||||||
# miningByStakingAddress |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0x00535175000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3" |
|
||||||
} |
|
||||||
|
|
||||||
# isPoolActive |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0xa711e6a1000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
|
||||||
} |
|
||||||
|
|
||||||
# poolDelegators |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0x9ea8082b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: |
|
||||||
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491" |
|
||||||
} |
|
||||||
|
|
||||||
# stakeAmountTotalMinusOrderedWithdraw |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0x234fbf2b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x00000000000000000000000000000000000000000000000029a2241af62c0000" |
|
||||||
} |
|
||||||
|
|
||||||
# stakeAmountMinusOrderedWithdraw |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
jsonrpc: "2.0", |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0x58daab6a000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000db9cb2478d917719c53862008672166808258577", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000001bc16d674ec80000" |
|
||||||
} |
|
||||||
|
|
||||||
# isValidator |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0xfacd743b000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000001" |
|
||||||
} |
|
||||||
|
|
||||||
# validatorCounter |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0xb41832e4000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000002" |
|
||||||
} |
|
||||||
|
|
||||||
# isValidatorBanned |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0xa92252ae000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
|
||||||
} |
|
||||||
|
|
||||||
# bannedUntil |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0x5836d08a000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
|
||||||
} |
|
||||||
|
|
||||||
# banCounter |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{data: "0x1d0cd4c6000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
|
||||||
} |
|
||||||
|
|
||||||
# DELEGATOR |
|
||||||
# stakeAmount |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0xa697ecff000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
} |
|
||||||
|
|
||||||
# orderedWithdrawAmount |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0xe9ab0300000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
|
||||||
} |
|
||||||
|
|
||||||
# maxWithdrawAllowed |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0x6bda1577000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
} |
|
||||||
|
|
||||||
# maxWithdrawOrderAllowed |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0x950a6513000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
} |
|
||||||
|
|
||||||
# orderWithdrawEpoch |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0xa4205967000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", |
|
||||||
to: _ |
|
||||||
}, |
|
||||||
"latest" |
|
||||||
] |
|
||||||
} -> |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
result: "0x0000000000000000000000000000000000000000000000000000000000000000" |
|
||||||
} |
|
||||||
end)} |
|
||||||
end |
|
||||||
) |
|
||||||
end |
|
||||||
end |
|
@ -1,140 +0,0 @@ |
|||||||
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.Chain.StakingPool |
|
||||||
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_hash: 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_hash: 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} = pool, {failed, success} -> |
|
||||||
{[pool | failed], success} |
|
||||||
|
|
||||||
pool, {failed, success} -> |
|
||||||
changeset = StakingPool.changeset(%StakingPool{}, pool) |
|
||||||
|
|
||||||
if changeset.valid? do |
|
||||||
{failed, [changeset.changes | success]} |
|
||||||
else |
|
||||||
{[pool | failed], success} |
|
||||||
end |
|
||||||
end) |
|
||||||
|
|
||||||
import_params = %{ |
|
||||||
staking_pools: %{params: remove_assoc(success)}, |
|
||||||
staking_pools_delegators: %{params: delegators_list(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 delegators_list(pools) do |
|
||||||
Enum.reduce(pools, [], fn pool, acc -> |
|
||||||
pool.delegators |
|
||||||
|> Enum.map(&Map.get(&1, :changes)) |
|
||||||
|> Enum.concat(acc) |
|
||||||
end) |
|
||||||
end |
|
||||||
|
|
||||||
defp remove_assoc(pools) do |
|
||||||
Enum.map(pools, &Map.delete(&1, :delegators)) |
|
||||||
end |
|
||||||
end |
|
@ -1,224 +0,0 @@ |
|||||||
defmodule Indexer.Fetcher.StakingPoolsTest do |
|
||||||
use EthereumJSONRPC.Case |
|
||||||
use Explorer.DataCase |
|
||||||
|
|
||||||
import Mox |
|
||||||
|
|
||||||
alias Indexer.Fetcher.StakingPools |
|
||||||
alias Explorer.Staking.PoolsReader |
|
||||||
alias Explorer.Chain.StakingPool |
|
||||||
|
|
||||||
@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_hash) |
|
||||||
|
|
||||||
get_pool_data_from_blockchain() |
|
||||||
|
|
||||||
assert {:retry, retry_list} = StakingPools.run(list, nil) |
|
||||||
assert Enum.count(retry_list) == 2 |
|
||||||
|
|
||||||
pool = Explorer.Repo.get_by(StakingPool, staking_address_hash: success_address) |
|
||||||
assert pool.is_active == true |
|
||||||
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" |
|
||||||
} |
|
||||||
|
|
||||||
# stakeAmountMinusOrderedWithdraw |
|
||||||
%{ |
|
||||||
id: id, |
|
||||||
jsonrpc: "2.0", |
|
||||||
method: "eth_call", |
|
||||||
params: [ |
|
||||||
%{ |
|
||||||
data: |
|
||||||
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", |
|
||||||
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