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