diff --git a/apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex index c4d9c1f8f8..60c943ef70 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex @@ -361,110 +361,102 @@ defmodule BlockScoutWeb.StakesChannel do def find_claim_reward_pools(socket, staker, staking_contract_address) do :ets.insert(ContractState, {claim_reward_long_op_key(staker), true}) try do - staker_padded = address_pad_to_64(staker) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - # Search for `PlacedStake` events - {error, pools_staked_into} = find_claim_reward_pools_by_logs(staking_contract_address, [ - # keccak-256 of `PlacedStake(address,address,uint256,uint256)` - "0x2273de02cb1f69ba6259d22c4bc22c60e4c94c193265ef6afee324a04a9b6d22", - nil, # don't filter by `toPoolStakingAddress` - "0x" <> staker_padded # filter by `staker` - ], json_rpc_named_arguments) - - # Search for `MovedStake` events - {error, pools_moved_into} = if error == nil do - find_claim_reward_pools_by_logs(staking_contract_address, [ - # keccak-256 of `MovedStake(address,address,address,uint256,uint256)` - "0x4480d8e4b1e9095b94bf513961d26fe1d32386ebdd103d18fe8738cf4b2223ff", - nil, # don't filter by `toPoolStakingAddress` - "0x" <> staker_padded # filter by `staker` - ], json_rpc_named_arguments) + staking_contract = ContractState.get(:staking_contract) + + responses = + ContractReader.get_staker_pools_length_request(staker) + |> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi) + staker_pools_length = responses[:length] + + chunk_size = 100 + pools = if staker_pools_length > 0 do + chunks = 0..trunc(ceil(staker_pools_length / chunk_size) - 1) + Enum.reduce(chunks, [], fn i, acc -> + responses = + ContractReader.get_staker_pools_request(staker, i * chunk_size, chunk_size) + |> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi) + acc ++ Enum.map(responses[:pools], fn pool_staking_address -> + address_bytes_to_string(pool_staking_address) + end) + end) else - {error, []} + [] end - {error, pools} = if error == nil do - pools = Enum.uniq(pools_staked_into ++ pools_moved_into) + pools_amounts = Enum.map(pools, fn pool_staking_address -> + ContractReader.call_get_reward_amount( + staking_contract_address, + [], + pool_staking_address, + staker, + json_rpc_named_arguments + ) + end) - pools_amounts = Enum.map(pools, fn pool_staking_address -> - ContractReader.call_get_reward_amount( + error = Enum.find_value(pools_amounts, fn result -> + case result do + {:error, reason} -> error_reason_to_string(reason) + _ -> nil + end + end) + + {error, pools} = if error != nil do + {error, %{}} + else + block_reward_contract = ContractState.get(:block_reward_contract) + + pools = + pools_amounts + |> Enum.map(fn {_, amounts} -> amounts end) + |> Enum.zip(pools) + |> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end) + |> Enum.map(fn {amounts, pool_staking_address} -> + responses = + ContractReader.epochs_to_claim_reward_from_request(pool_staking_address, staker) + |> ContractReader.perform_requests(%{block_reward: block_reward_contract.address}, block_reward_contract.abi) + + epochs = + array_to_ranges(responses[:epochs]) + |> Enum.map(fn {first, last} -> + Integer.to_string(first) <> (if first != last, do: "-" <> Integer.to_string(last), else: "") + end) + data = Map.put(amounts, :epochs, Enum.join(epochs, ",")) + + {data, pool_staking_address} + end) + |> Enum.filter(fn {data, _} -> data.epochs != "" end) + + pools_gas_estimates = Enum.map(pools, fn {_data, pool_staking_address} -> + result = ContractReader.claim_reward_estimate_gas( staking_contract_address, [], pool_staking_address, staker, json_rpc_named_arguments ) + {pool_staking_address, result} end) - error = Enum.find_value(pools_amounts, fn result -> + error = Enum.find_value(pools_gas_estimates, fn {_, result} -> case result do {:error, reason} -> error_reason_to_string(reason) _ -> nil end end) - {error, pools} = if error != nil do - {error, %{}} - else - block_reward_contract = ContractState.get(:block_reward_contract) - - pools = - pools_amounts - |> Enum.map(fn {_, amounts} -> amounts end) - |> Enum.zip(pools) - |> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end) - |> Enum.map(fn {amounts, pool_staking_address} -> - responses = - ContractReader.epochs_to_claim_reward_from_request(pool_staking_address, staker) - |> ContractReader.perform_requests(%{block_reward: block_reward_contract.address}, block_reward_contract.abi) - - epochs = - array_to_ranges(responses[:epochs]) - |> Enum.map(fn {first, last} -> - Integer.to_string(first) <> (if first != last, do: "-" <> Integer.to_string(last), else: "") - end) - data = Map.put(amounts, :epochs, Enum.join(epochs, ",")) - - {data, pool_staking_address} - end) - |> Enum.filter(fn {data, _} -> data.epochs != "" end) - - pools_gas_estimates = Enum.map(pools, fn {_data, pool_staking_address} -> - result = ContractReader.claim_reward_estimate_gas( - staking_contract_address, - [], - pool_staking_address, - staker, - json_rpc_named_arguments - ) - {pool_staking_address, result} + pools = if error == nil do + pools_gas_estimates = Map.new(pools_gas_estimates) + Map.new(pools, fn {data, pool_staking_address} -> + {:ok, estimate} = pools_gas_estimates[pool_staking_address] + data = Map.put(data, :gas_estimate, estimate) + {pool_staking_address, data} end) - - error = Enum.find_value(pools_gas_estimates, fn {_, result} -> - case result do - {:error, reason} -> error_reason_to_string(reason) - _ -> nil - end - end) - - pools = if error == nil do - pools_gas_estimates = Map.new(pools_gas_estimates) - Map.new(pools, fn {data, pool_staking_address} -> - {:ok, estimate} = pools_gas_estimates[pool_staking_address] - data = Map.put(data, :gas_estimate, estimate) - {pool_staking_address, data} - end) - else - %{} - end - - {error, pools} + else + %{} end {error, pools} - else - {error, %{}} end html = View.render_to_string( @@ -548,47 +540,7 @@ defmodule BlockScoutWeb.StakesChannel do end end - defp find_claim_reward_pools_by_logs(staking_contract_address, topics, json_rpc_named_arguments) do - latest_block = BlockNumber.get_max() - split_by = 500 # must be less than 1000 - - iterations = 0..trunc(ceil(latest_block / split_by) - 1) - Enum.reduce(iterations, {nil, []}, fn i, acc -> - {acc_error, acc_pools} = acc - if acc_error do - {acc_error, []} - else - from = i * split_by + 1 - to = (i + 1) * split_by - to = if to > latest_block, do: latest_block, else: to - result = EthereumJSONRPC.request(%{ - id: 0, - method: "eth_getLogs", - params: [%{ - fromBlock: "0x" <> Integer.to_string(from, 16), - toBlock: "0x" <> Integer.to_string(to, 16), - address: staking_contract_address, - topics: topics - }] - }) |> EthereumJSONRPC.json_rpc(json_rpc_named_arguments) - case result do - {:ok, response} -> - pools = Enum.uniq(acc_pools ++ Enum.map(response, fn event -> - truncate_address(Enum.at(event["topics"], 1)) - end)) - {acc_error, pools} - {:error, reason} -> - {error_reason_to_string(reason), []} - end - end - end) - end - - defp address_pad_to_64(address) do - address - |> String.replace_leading("0x", "") - |> String.pad_leading(64, ["0"]) - end + defp address_bytes_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) defp array_to_ranges(numbers, prev_ranges \\ []) do length = Enum.count(numbers) @@ -647,8 +599,4 @@ defmodule BlockScoutWeb.StakesChannel do staker = if staker == nil, do: "", else: staker Atom.to_string(@claim_reward_long_op) <> "_" <> staker end - - defp truncate_address("0x000000000000000000000000" <> truncated_address) do - "0x#{truncated_address}" - end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_title.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_title.html.eex index 6b1d71401f..40342d6967 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_title.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_title.html.eex @@ -11,7 +11,7 @@
-
<%= gettext("Show only those I staked into") %>
+
<%= gettext("Show only those I have stake in") %>
\ No newline at end of file diff --git a/apps/explorer/lib/explorer/staking/contract_reader.ex b/apps/explorer/lib/explorer/staking/contract_reader.ex index f108b51a0e..0041d33850 100644 --- a/apps/explorer/lib/explorer/staking/contract_reader.ex +++ b/apps/explorer/lib/explorer/staking/contract_reader.ex @@ -162,6 +162,18 @@ defmodule Explorer.Staking.ContractReader do ] end + def get_staker_pools_request(staker, offset, length) do + [ + pools: {:staking, "getStakerPools", [staker, offset, length]} + ] + end + + def get_staker_pools_length_request(staker) do + [ + length: {:staking, "getStakerPoolsLength", [staker]} + ] + end + def pool_staking_requests(staking_address, block_number) do [ active_delegators: active_delegators_request(staking_address, block_number)[:active_delegators], diff --git a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json b/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json index d8ff094c51..bf9cebbb58 100644 --- a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json +++ b/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json @@ -36,6 +36,52 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [ + { + "name": "_staker", + "type": "address" + }, + { + "name": "_offset", + "type": "uint256" + }, + { + "name": "_length", + "type": "uint256" + } + ], + "name": "getStakerPools", + "outputs": [ + { + "name": "result", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_staker", + "type": "address" + } + ], + "name": "getStakerPoolsLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [