Merge remote-tracking branch 'origin/master' into vb-besu-support

vb-besu-support
Victor Baranov 5 years ago
commit 06f917eacb
  1. 2
      .dialyzer-ignore
  2. 33
      CHANGELOG.md
  3. 4
      apps/block_scout_web/config/config.exs
  4. 9
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex
  5. 18
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  6. 12
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  7. 8
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  8. 11
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  9. 1
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  10. 10
      apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex
  11. 17
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  12. 2
      apps/block_scout_web/mix.exs
  13. 113
      apps/block_scout_web/priv/gettext/default.pot
  14. 113
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  15. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_coin_balance_by_day_controller_test.exs
  16. 71
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  17. 5
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  18. 183
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  19. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/verified_smart_contract_controller_test.exs
  20. 1
      apps/block_scout_web/test/support/fixture/smart_contract/contract_with_lib.json
  21. 34
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  22. 45
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
  23. 10
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
  24. 16
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  25. 4
      apps/ethereum_jsonrpc/mix.exs
  26. 218
      apps/explorer/lib/explorer/chain.ex
  27. 68
      apps/explorer/lib/explorer/chain/address/coin_balance.ex
  28. 74
      apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex
  29. 7
      apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex
  30. 42
      apps/explorer/lib/explorer/chain/cache/pending_transactions.ex
  31. 139
      apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex
  32. 3
      apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
  33. 1
      apps/explorer/lib/explorer/chain/smart_contract.ex
  34. 5
      apps/explorer/lib/explorer/chain/token_transfer.ex
  35. 8
      apps/explorer/lib/explorer/chain/transaction.ex
  36. 22
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  37. 30
      apps/explorer/lib/explorer/counters/last_fetched_counter.ex
  38. 1
      apps/explorer/lib/explorer/etherscan.ex
  39. 9
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  40. 164
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  41. 230
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  42. 12
      apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
  43. 2
      apps/explorer/mix.exs
  44. 52
      apps/explorer/package-lock.json
  45. 3
      apps/explorer/package.json
  46. 3
      apps/explorer/priv/compile_solc.js
  47. 7
      apps/explorer/priv/repo/migrations/20200518075748_create_index_blocks_miner_hash_number_index.exs
  48. 7
      apps/explorer/priv/repo/migrations/20200521170002_create_token_transfers_token_contract_address_hash_token_id_block_number_index.exs
  49. 17
      apps/explorer/priv/repo/migrations/20200525115811_address_coin_balances_daily.exs
  50. 12
      apps/explorer/priv/repo/migrations/20200527144742_add_counters_table.exs
  51. 9
      apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs
  52. 2
      apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
  53. 8
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  54. 78
      apps/explorer/test/explorer/chain_spec/parity/importer_test.exs
  55. 58
      apps/explorer/test/explorer/chain_test.exs
  56. 11
      apps/explorer/test/explorer/smart_contract/publisher_test.exs
  57. 6
      apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
  58. 484
      apps/explorer/test/explorer/smart_contract/verifier_test.exs
  59. 15
      apps/explorer/test/support/factory.ex
  60. 2
      apps/explorer/test/support/fixture/smart_contract/compiler_tests.json
  61. 18
      apps/explorer/test/support/fixture/smart_contract/contract_from_factory.sol
  62. 2
      apps/explorer/test/support/fixture/smart_contract/contract_with_lib.json
  63. 25
      apps/explorer/test/support/fixture/smart_contract/contract_with_lib.sol
  64. 1
      apps/explorer/test/support/fixture/smart_contract/solidity_5.11_new_whisper_metadata.json
  65. 412
      apps/explorer/test/support/fixture/smart_contract/solidity_5.11_new_whisper_metadata.sol
  66. 11
      apps/indexer/lib/indexer/block/fetcher.ex
  67. 45
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  68. 6
      apps/indexer/lib/indexer/fetcher/block_reward.ex
  69. 101
      apps/indexer/lib/indexer/fetcher/coin_balance.ex
  70. 55
      apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
  71. 155
      apps/indexer/lib/indexer/transform/address_coin_balances_daily.ex
  72. 135
      apps/indexer/test/indexer/block/fetcher_test.exs
  73. 120
      apps/indexer/test/indexer/block/realtime/fetcher_test.exs
  74. 126
      apps/indexer/test/indexer/fetcher/block_reward_test.exs
  75. 65
      apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs
  76. 137
      apps/indexer/test/indexer/fetcher/coin_balance_test.exs
  77. 4
      mix.lock

@ -13,3 +13,5 @@ apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == '
lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324
lib/block_scout_web/views/layout_view.ex:143
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22

@ -1,11 +1,44 @@
## Current
### Features
- [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint.
### Fixes
### Chore
- [#3152](https://github.com/poanetwork/blockscout/pull/3152) - Fix contract compilation tests for old versions of compiler
## 3.1.3-beta
### Features
- [#3125](https://github.com/poanetwork/blockscout/pull/3125) - Availability to configure a number of days to consider at coin balance history chart via environment variable
### Fixes
- [#3146](https://github.com/poanetwork/blockscout/pull/3146) - Fix coin balance history page: order of items, fix if no balance changes
- [#3142](https://github.com/poanetwork/blockscout/pull/3142) - Speed-up last coin balance timestamp query (coin balance history page performance improvement)
- [#3140](https://github.com/poanetwork/blockscout/pull/3140) - Fix performance of the balance changing history list loading
- [#3133](https://github.com/poanetwork/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests
- [#3132](https://github.com/poanetwork/blockscout/pull/3132) - Fix performance of coin supply API endpoints
- [#3130](https://github.com/poanetwork/blockscout/pull/3130) - Take into account FIRST_BLOCK for block rewards fetching
- [#3128](https://github.com/poanetwork/blockscout/pull/3128) - Token instance metadata retriever refinement: add processing of token metadata if only image URL is passed to token URI
- [#3126](https://github.com/poanetwork/blockscout/pull/3126) - Fetch balance only for blocks which are greater or equal block with FIRST_BLOCK number
- [#3125](https://github.com/poanetwork/blockscout/pull/3125) - Fix performance of coin balance history chart
- [#3122](https://github.com/poanetwork/blockscout/pull/3122) - Exclude balance percentage calculation for burn address on accounts page
- [#3121](https://github.com/poanetwork/blockscout/pull/3121) - Geth: handle response from eth_getblockbyhash JSON RPC method without totalDifficulty (uncle blocks)
- [#3119](https://github.com/poanetwork/blockscout/pull/3119), [#3120](https://github.com/poanetwork/blockscout/pull/3120) - Fix performance of Inventory tab loading for ERC-721 tokens
- [#3114](https://github.com/poanetwork/blockscout/pull/3114) - Fix performance of "Blocks validated" page
- [#3112](https://github.com/poanetwork/blockscout/pull/3112) - Fix verification of contracts, compiled with nightly builds of solc compiler
- [#3112](https://github.com/poanetwork/blockscout/pull/3112) - Check compiler version at contract verification
- [#3106](https://github.com/poanetwork/blockscout/pull/3106) - Fix verification of contracts with `immutable` declaration
- [#3106](https://github.com/poanetwork/blockscout/pull/3106), [#3115](https://github.com/poanetwork/blockscout/pull/3115) - Fix verification of contracts, created from factory (from internal transaction)
### Chore
- [#3137](https://github.com/poanetwork/blockscout/pull/3137) - RSK Papyrus Release v2.0.1 hardfork: cumulativeDifficulty
- [#3134](https://github.com/poanetwork/blockscout/pull/3134) - Get last value of fetched coinsupply API endpoint from DB if cache is empty
- [#3124](https://github.com/poanetwork/blockscout/pull/3124) - Display upper border for tx speed if the value cannot be calculated
## 3.1.2-beta

@ -84,6 +84,10 @@ config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController,
# days
history_size: 30
config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance,
# days
coin_balance_history_days: System.get_env("COIN_BALANCE_HISTORY_DAYS", "10")
config :ex_cldr,
default_locale: "en",
default_backend: BlockScoutWeb.Cldr

@ -44,8 +44,15 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
def coinsupply(conn, _params) do
cached_coin_total_supply_wei = AddressSumMinusBurnt.get_sum_minus_burnt()
coin_total_supply_wei =
if Decimal.cmp(cached_coin_total_supply_wei, 0) == :gt do
cached_coin_total_supply_wei
else
Chain.get_last_fetched_counter("sum_coin_total_supply_minus_burnt")
end
cached_coin_total_supply =
%Wei{value: Decimal.new(cached_coin_total_supply_wei)}
%Wei{value: Decimal.new(coin_total_supply_wei)}
|> Wei.to(:ether)
render(conn, "coinsupply.json", cached_coin_total_supply)

@ -5,16 +5,30 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
alias Explorer.Chain
alias Explorer.Chain.Transaction
def gettxinfo(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
{:transaction, {:ok, %Transaction{revert_reason: revert_reason, error: error} = transaction}} <-
transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs)
transaction_updated =
if error == "Reverted" do
if revert_reason == nil do
%Transaction{transaction | revert_reason: Chain.fetch_tx_revert_reason(transaction)}
else
transaction
end
else
transaction
end
render(conn, :gettxinfo, %{
transaction: transaction,
transaction: transaction_updated,
block_height: Chain.block_height(),
logs: logs,
next_page_params: next_page_params(next_page, logs, params)

@ -470,7 +470,8 @@ defmodule BlockScoutWeb.Etherscan do
"success" => true,
"timeStamp" => "1541018182",
"to" => "0x000000000000000000000000000000000000000d",
"value" => "67612"
"value" => "67612",
revertReason: "No credit of that type"
}
}
@ -609,6 +610,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("18")
}
@revert_reason_type %{
type: "revert_reason",
definition: "Revert reason of transaction.",
example: ~s("No credit of that type")
}
@logs_details %{
name: "Log Detail",
fields: %{
@ -1010,7 +1017,8 @@ defmodule BlockScoutWeb.Etherscan do
logs: %{
type: "array",
array_type: @logs_details
}
},
revertReason: @revert_reason_type
}
}

@ -53,6 +53,14 @@
<!-- Verify in other explorers -->
<!-- <%= render BlockScoutWeb.AddressView, "_verify_other_explorers.html", hash: hash(@transaction), type: "tx" %> -->
<hr>
<%= if status == {:error, "Reverted"} do %>
<dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Revert reason" %> </dt>
<dd class="col-sm-9" data-selector="block-number">
<%= BlockScoutWeb.TransactionView.transaction_revert_reason(@transaction) %>
</dd>
</dl>
<% end %>
<!-- Block Hash -->
<dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Block Number" %> </dt>

@ -116,6 +116,17 @@ defmodule BlockScoutWeb.AddressView do
def balance_percentage(_, nil), do: ""
def balance_percentage(
%Address{
hash: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
}
},
_
),
do: ""
def balance_percentage(%Address{fetched_coin_balance: balance}, total_supply) do
if total_supply > 0 do
balance

@ -70,6 +70,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
"gasUsed" => "#{transaction.gas_used}",
"gasPrice" => "#{transaction.gas_price.value}",
"logs" => Enum.map(logs, &prepare_log/1),
"revertReason" => "#{transaction.revert_reason}",
"next_page_params" => next_page_params
}
end

@ -26,7 +26,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
instance.metadata["image_url"]
instance.metadata && instance.metadata["image"] ->
instance.metadata["image"]
retrieve_image(instance.metadata["image"])
instance.metadata && instance.metadata["properties"]["image"]["description"] ->
instance.metadata["properties"]["image"]["description"]
@ -87,6 +87,14 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
|> tab_name()
end
defp retrieve_image(image) when is_map(image) do
image["description"]
end
defp retrieve_image(image) do
image
end
defp tab_name(["token_transfers"]), do: gettext("Token Transfers")
defp tab_name(["metadata"]), do: gettext("Metadata")
end

@ -6,6 +6,7 @@ defmodule BlockScoutWeb.TransactionView do
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei}
alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token
alias Timex.Duration
@ -144,7 +145,17 @@ defmodule BlockScoutWeb.TransactionView do
end
def processing_time_duration(%Transaction{earliest_processing_start: nil}) do
:unknown
avg_time = AverageBlockTime.average_block_time()
if avg_time == {:error, :disabled} do
:unknown
else
avg_time_in_secs =
avg_time
|> Duration.to_seconds()
{:ok, "<= #{avg_time_in_secs} seconds"}
end
end
def processing_time_duration(%Transaction{
@ -226,6 +237,10 @@ defmodule BlockScoutWeb.TransactionView do
Chain.transaction_to_status(transaction)
end
def transaction_revert_reason(transaction) do
Chain.transaction_to_revert_reason(transaction)
end
def empty_exchange_rate?(exchange_rate) do
Token.null?(exchange_rate)
end

@ -90,7 +90,7 @@ defmodule BlockScoutWeb.Mixfile do
{:floki, "~> 0.20.1", only: :test},
{:flow, "~> 0.12"},
{:gettext, "~> 0.16.1"},
{:httpoison, "~> 1.0"},
{:httpoison, "~> 1.6"},
{:indexer, in_umbrella: true, runtime: false},
# JSON parser and generator
{:jason, "~> 1.0"},

@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:236
#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:72
#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@ -213,12 +213,12 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:32
#: lib/block_scout_web/views/transaction_view.ex:33
msgid "Block Pending"
msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:325
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:322
#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@ -558,12 +558,12 @@ msgid "During times when the network is busy (i.e during ICOs) it can take a whi
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:136
#: lib/block_scout_web/views/transaction_view.ex:137
msgid "ERC-20 "
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137
#: lib/block_scout_web/views/transaction_view.ex:138
msgid "ERC-721 "
msgstr ""
@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:240
#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:238
#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/templates/transaction/overview.html.eex:255
#: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@ -666,7 +666,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
#: lib/block_scout_web/templates/transaction/overview.html.eex:79
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@ -753,8 +753,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115
#: lib/block_scout_web/templates/transaction/overview.html.eex:119
#: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@ -776,7 +776,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:237
#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@ -789,7 +789,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@ -891,7 +891,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:318
#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@ -903,10 +903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:314
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:379
#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@ -922,7 +922,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
#: lib/block_scout_web/views/transaction_view.ex:328
#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283
#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
@ -1041,12 +1041,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:127
#: lib/block_scout_web/views/address_view.ex:138
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:221
#: lib/block_scout_web/views/transaction_view.ex:232
msgid "Max of"
msgstr ""
@ -1159,8 +1159,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
#: lib/block_scout_web/views/transaction_view.ex:235
#: lib/block_scout_web/views/transaction_view.ex:269
#: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@ -1201,14 +1201,14 @@ msgid "RPC"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:382
#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:101
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@ -1518,7 +1518,7 @@ msgid "Type"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122
#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277
#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/templates/transaction/overview.html.eex:255
#: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273
#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@ -1779,7 +1779,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:91
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:99
msgid "Metadata"
msgstr ""
@ -1799,15 +1799,15 @@ msgid "Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134
#: lib/block_scout_web/templates/transaction/overview.html.eex:147
#: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:319
#: lib/block_scout_web/views/address_view.ex:330
msgid "Blocks Validated"
msgstr ""
@ -1817,18 +1817,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:315
#: lib/block_scout_web/views/address_view.ex:326
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:318
#: lib/block_scout_web/views/address_view.ex:329
msgid "Coin Balance History"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:316
#: lib/block_scout_web/views/address_view.ex:327
msgid "Decompiled Code"
msgstr ""
@ -1837,8 +1837,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:313
#: lib/block_scout_web/views/transaction_view.ex:380
#: lib/block_scout_web/views/address_view.ex:324
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,15 +1847,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:320
#: lib/block_scout_web/views/transaction_view.ex:381
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:317
#: lib/block_scout_web/views/address_view.ex:328
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -1864,7 +1864,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:311
#: lib/block_scout_web/views/address_view.ex:322
msgid "Tokens"
msgstr ""
@ -1876,31 +1876,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:312
#: lib/block_scout_web/views/address_view.ex:323
msgid "Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:232
#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
#: lib/block_scout_web/views/transaction_view.ex:317
#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:316
#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""

@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:236
#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:72
#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@ -213,12 +213,12 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:32
#: lib/block_scout_web/views/transaction_view.ex:33
msgid "Block Pending"
msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:325
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:322
#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@ -558,12 +558,12 @@ msgid "During times when the network is busy (i.e during ICOs) it can take a whi
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:136
#: lib/block_scout_web/views/transaction_view.ex:137
msgid "ERC-20 "
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137
#: lib/block_scout_web/views/transaction_view.ex:138
msgid "ERC-721 "
msgstr ""
@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:240
#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:238
#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/templates/transaction/overview.html.eex:255
#: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@ -666,7 +666,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
#: lib/block_scout_web/templates/transaction/overview.html.eex:79
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@ -753,8 +753,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115
#: lib/block_scout_web/templates/transaction/overview.html.eex:119
#: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@ -776,7 +776,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:237
#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@ -789,7 +789,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@ -891,7 +891,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:318
#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@ -903,10 +903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:314
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:379
#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@ -922,7 +922,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
#: lib/block_scout_web/views/transaction_view.ex:328
#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283
#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
@ -1041,12 +1041,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:127
#: lib/block_scout_web/views/address_view.ex:138
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:221
#: lib/block_scout_web/views/transaction_view.ex:232
msgid "Max of"
msgstr ""
@ -1159,8 +1159,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
#: lib/block_scout_web/views/transaction_view.ex:235
#: lib/block_scout_web/views/transaction_view.ex:269
#: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@ -1201,14 +1201,14 @@ msgid "RPC"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:382
#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:101
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@ -1518,7 +1518,7 @@ msgid "Type"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122
#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277
#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/templates/transaction/overview.html.eex:255
#: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273
#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@ -1779,7 +1779,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:91
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:99
msgid "Metadata"
msgstr ""
@ -1799,15 +1799,15 @@ msgid "Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134
#: lib/block_scout_web/templates/transaction/overview.html.eex:147
#: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:319
#: lib/block_scout_web/views/address_view.ex:330
msgid "Blocks Validated"
msgstr ""
@ -1817,18 +1817,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:315
#: lib/block_scout_web/views/address_view.ex:326
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:318
#: lib/block_scout_web/views/address_view.ex:329
msgid "Coin Balance History"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:316
#: lib/block_scout_web/views/address_view.ex:327
msgid "Decompiled Code"
msgstr ""
@ -1837,8 +1837,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:313
#: lib/block_scout_web/views/transaction_view.ex:380
#: lib/block_scout_web/views/address_view.ex:324
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,15 +1847,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:320
#: lib/block_scout_web/views/transaction_view.ex:381
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:317
#: lib/block_scout_web/views/address_view.ex:328
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -1864,7 +1864,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:311
#: lib/block_scout_web/views/address_view.ex:322
msgid "Tokens"
msgstr ""
@ -1876,31 +1876,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:312
#: lib/block_scout_web/views/address_view.ex:323
msgid "Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:232
#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
#: lib/block_scout_web/views/transaction_view.ex:317
#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:316
#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""

@ -11,6 +11,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceByDayControllerTest do
block_one_day_ago = insert(:block, timestamp: Timex.shift(noon, days: -1), number: 1)
insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block.number)
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: noon)
insert(:fetched_balance_daily, address_hash: address.hash, value: 2000, day: Timex.shift(noon, days: -1))
conn = get(conn, address_coin_balance_by_day_path(conn, :index, Address.checksum(address)), %{"type" => "JSON"})

@ -111,6 +111,30 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
address_hash = to_string(address.hash)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{
id: id,
method: "eth_getBalance",
params: [^mining_address_hash, "0x65"]
}
],
_options ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end)
res = eth_block_number_fake_response("0x65")
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{
id: 0,
method: "eth_getBlockByNumber",
params: ["0x65", true]
}
],
_ ->
{:ok, [res]}
end)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{
id: id,
@ -122,6 +146,17 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{
id: 0,
method: "eth_getBlockByNumber",
params: ["0x65", true]
}
],
_ ->
{:ok, [res]}
end)
response =
conn
|> get("/api", params)
@ -2719,4 +2754,40 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> put_in(["properties", "result"], result)
|> ExJsonSchema.Schema.resolve()
end
defp eth_block_number_fake_response(block_quantity) do
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x0000000000000000000000000000000000000000",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end
end

@ -609,6 +609,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
insert(:transaction, created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
params = %{
"module" => "contract",
@ -658,10 +659,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"name" => name,
"optimize" => optimize,
"contract" => contract_source_code,
"expected_bytecode" => expected_bytecode
"expected_bytecode" => expected_bytecode,
"tx_input" => tx_input
} = contract_data
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
insert(:transaction, created_contract_address_hash: contract_address.hash, input: "0x" <> tx_input)
params = %{
"module" => "contract",

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
use BlockScoutWeb.ConnCase
import Mox
@moduletag capture_log: true
describe "gettxreceiptstatus" do
@ -520,7 +522,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"index" => "#{log.index}"
}
],
"next_page_params" => nil
"next_page_params" => nil,
"revertReason" => ""
}
schema =
@ -576,6 +579,184 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with a txhash with revert reason from DB", %{conn: conn} do
block = insert(:block, number: 100)
transaction =
:transaction
|> insert(revert_reason: "No credit of that type")
|> with_block(block)
insert(:address)
params = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"]["revertReason"] == "No credit of that type"
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with a txhash with empty revert reason from DB", %{conn: conn} do
block = insert(:block, number: 100)
transaction =
:transaction
|> insert(revert_reason: "")
|> with_block(block)
insert(:address)
params = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"]["revertReason"] == ""
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with a txhash with revert reason from the archive node", %{conn: conn} do
block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
transaction =
:transaction
|> insert(
error: "Reverted",
status: :error,
block_hash: block.hash,
block_number: block.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
insert(:address)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _json, [] ->
{:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
end
)
params = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"]["revertReason"] == "No credit of that type"
assert response["status"] == "1"
assert response["message"] == "OK"
end
end
test "with a txhash with empty revert reason from the archive node", %{conn: conn} do
block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
transaction =
:transaction
|> insert(
error: "Reverted",
status: :error,
block_hash: block.hash,
block_number: block.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
insert(:address)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _json, [] ->
{:error, %{code: -32015, message: "VM execution error.", data: ""}}
end
)
params = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"]["revertReason"] == ""
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with a txhash with empty revert reason from DB if eth_call doesn't return an error", %{conn: conn} do
block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
transaction =
:transaction
|> insert(
error: "Reverted",
status: :error,
block_hash: block.hash,
block_number: block.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
insert(:address)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _json, [] ->
{:ok}
end
)
params = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"]["revertReason"] == ""
assert response["status"] == "1"
assert response["message"] == "OK"
end
defp resolve_schema(result \\ %{}) do

@ -12,6 +12,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedControllerTest do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
insert(:transaction, created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
params = %{
"address_hash" => to_string(contract_address.hash),
@ -40,10 +41,12 @@ defmodule BlockScoutWeb.API.V1.VerifiedControllerTest do
"name" => name,
"optimize" => optimize,
"contract" => contract_source_code,
"tx_input" => tx_input,
"expected_bytecode" => expected_bytecode
} = contract_data
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
insert(:transaction, created_contract_address_hash: contract_address.hash, input: "0x" <> tx_input)
params = %{
"address_hash" => to_string(contract_address.hash),

@ -3,6 +3,7 @@
"compiler_version": "v0.5.11+commit.c082d0b4",
"contract": "pragma solidity 0.5.11;library BadSafeMath { function add(uint256 a, uint256 b) public pure returns (uint256) { uint256 c = a + 2 * b; require(c >= a, \"SafeMath: addition overflow\"); return c; }}contract SimpleStorage { uint256 storedData = 10; using BadSafeMath for uint256; function increment(uint256 x) public { storedData = storedData.add(x); } function set(uint256 x) public { storedData = x; } function get() public view returns (uint256) { return storedData; }}",
"expected_bytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158203e59bfb9a5a2e55d38231922c86d8b2ec9b66cb2f6595613674bc4e15290b60764736f6c634300050b0032",
"tx_input": "6080604052600a60005534801561001557600080fd5b50610169806100256000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158207809bc828bbcd3de3e6b6483facb0c5902901fc8827283c749c8ea0702eb871f64736f6c634300050b0032",
"external_libraries": {
"BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73"
},

@ -215,7 +215,18 @@ defmodule EthereumJSONRPC do
@spec fetch_beneficiaries([block_number], json_rpc_named_arguments) ::
{:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore
def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_beneficiaries(block_numbers, json_rpc_named_arguments)
min_block = first_block_to_fetch()
filtered_block_numbers =
block_numbers
|> Enum.filter(fn block_number ->
block_number >= min_block
end)
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_beneficiaries(
filtered_block_numbers,
json_rpc_named_arguments
)
end
@doc """
@ -294,9 +305,17 @@ defmodule EthereumJSONRPC do
@doc """
Fetches internal transactions for entire blocks from variant API.
"""
def fetch_block_internal_transactions(params_list, json_rpc_named_arguments) when is_list(params_list) do
def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
min_block = first_block_to_fetch()
filtered_block_numbers =
block_numbers
|> Enum.filter(fn block_number ->
block_number >= min_block
end)
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions(
params_list,
filtered_block_numbers,
json_rpc_named_arguments
)
end
@ -464,4 +483,13 @@ defmodule EthereumJSONRPC do
{:ok, Blocks.from_responses(responses, id_to_params)}
end
end
defp first_block_to_fetch do
string_value = Application.get_env(:indexer, :first_block)
case Integer.parse(string_value) do
{integer, ""} -> integer
_ -> 0
end
end
end

@ -235,6 +235,49 @@ defmodule EthereumJSONRPC.Block do
}
end
# Geth: a response from eth_getblockbyhash for uncle blocks is without `totalDifficulty` param
def elixir_to_params(
%{
"difficulty" => difficulty,
"extraData" => extra_data,
"gasLimit" => gas_limit,
"gasUsed" => gas_used,
"hash" => hash,
"logsBloom" => logs_bloom,
"miner" => miner_hash,
"number" => number,
"parentHash" => parent_hash,
"receiptsRoot" => receipts_root,
"sha3Uncles" => sha3_uncles,
"size" => size,
"stateRoot" => state_root,
"timestamp" => timestamp,
"transactionsRoot" => transactions_root,
"uncles" => uncles
} = elixir
) do
%{
difficulty: difficulty,
extra_data: extra_data,
gas_limit: gas_limit,
gas_used: gas_used,
hash: hash,
logs_bloom: logs_bloom,
miner_hash: miner_hash,
mix_hash: Map.get(elixir, "mixHash", "0x0"),
nonce: Map.get(elixir, "nonce", 0),
number: number,
parent_hash: parent_hash,
receipts_root: receipts_root,
sha3_uncles: sha3_uncles,
size: size,
state_root: state_root,
timestamp: timestamp,
transactions_root: transactions_root,
uncles: uncles
}
end
@doc """
Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0`
@ -437,7 +480,7 @@ defmodule EthereumJSONRPC.Block do
end
defp entry_to_elixir({key, quantity})
when key in ~w(difficulty gasLimit gasUsed minimumGasPrice number size totalDifficulty paidFees) and
when key in ~w(difficulty gasLimit gasUsed minimumGasPrice number size cumulativeDifficulty totalDifficulty paidFees) and
not is_nil(quantity) do
{key, quantity_to_integer(quantity)}
end

@ -58,16 +58,6 @@ defmodule EthereumJSONRPC.Encoder do
end)
end
def decode_result(result, %{returns: r} = fs) when r in [:string, [:string]] do
case decode_result(result, %{fs | returns: {:tuple, [:string]}}) do
{id, {:ok, [{string}]}} ->
{id, {:ok, [string]}}
error ->
error
end
end
def decode_result(%{id: id, result: result}, function_selector) do
types_list = List.wrap(function_selector.returns)

@ -9,7 +9,7 @@ defmodule EthereumJSONRPC.Transaction do
"""
require Logger
import EthereumJSONRPC, only: [quantity_to_integer: 1]
import EthereumJSONRPC, only: [quantity_to_integer: 1, integer_to_quantity: 1, request: 1]
alias EthereumJSONRPC
@ -313,6 +313,20 @@ defmodule EthereumJSONRPC.Transaction do
nil
end
def eth_call_request(id, block_number, data, to, from, gas, gas_price, value) do
block =
case block_number do
nil -> "latest"
block_number -> integer_to_quantity(block_number)
end
request(%{
id: id,
method: "eth_call",
params: [%{to: to, from: from, data: data, gas: gas, gas_price: gas_price, value: value}, block]
})
end
# double check that no new keys are being missed by requiring explicit match for passthrough
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format

@ -70,7 +70,7 @@ defmodule EthereumJsonrpc.MixProject do
# Code coverage
{:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"},
# JSONRPC HTTP Post calls
{:httpoison, "~> 1.0"},
{:httpoison, "~> 1.6"},
# Decode/Encode JSON for JSONRPC
{:jason, "~> 1.0"},
# Log errors and application output to separate files
@ -84,7 +84,7 @@ defmodule EthereumJsonrpc.MixProject do
# Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.6"},
# Encode/decode function names and arguments
{:ex_abi, [git: "https://github.com/poanetwork/ex_abi.git", branch: "master"]},
{:ex_abi, "~> 0.4"},
# `:verify_fun` for `Socket.Web.connect`
{:ssl_verify_fun, "~> 1.1"},
# `EthereumJSONRPC.WebSocket`

@ -28,11 +28,16 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain
alias Explorer.Chain.{
Address,
Address.CoinBalance,
Address.CoinBalanceDaily,
Address.CurrentTokenBalance,
Address.TokenBalance,
Block,
@ -1517,7 +1522,8 @@ defmodule Explorer.Chain do
from(
a0 in Address,
select: fragment("SUM(a0.fetched_coin_balance)"),
where: a0.hash != ^burn_address_hash
where: a0.hash != ^burn_address_hash,
where: a0.fetched_coin_balance > ^0
)
Repo.one!(query) || 0
@ -1528,7 +1534,8 @@ defmodule Explorer.Chain do
query =
from(
a0 in Address,
select: fragment("SUM(a0.fetched_coin_balance)")
select: fragment("SUM(a0.fetched_coin_balance)"),
where: a0.fetched_coin_balance > ^0
)
Repo.one!(query) || 0
@ -2160,6 +2167,27 @@ defmodule Explorer.Chain do
end
end
@spec upsert_last_fetched_counter(map()) :: {:ok, LastFetchedCounter.t()} | {:error, Ecto.Changeset.t()}
def upsert_last_fetched_counter(params) do
changeset = LastFetchedCounter.changeset(%LastFetchedCounter{}, params)
Repo.insert(changeset,
on_conflict: :replace_all,
conflict_target: [:counter_type]
)
end
def get_last_fetched_counter(type) do
query =
from(
last_fetched_counter in LastFetchedCounter,
where: last_fetched_counter.counter_type == ^type,
select: last_fetched_counter.value
)
Repo.one!(query) || 0
end
defp block_status({number, timestamp}) do
now = DateTime.utc_now()
last_block_period = DateTime.diff(now, timestamp, :millisecond)
@ -2689,6 +2717,68 @@ defmodule Explorer.Chain do
def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error}
def transaction_to_revert_reason(transaction) do
%Transaction{revert_reason: revert_reason} = transaction
if revert_reason == nil do
fetch_tx_revert_reason(transaction)
else
revert_reason
end
end
def fetch_tx_revert_reason(
%Transaction{
block_number: block_number,
to_address_hash: to_address_hash,
from_address_hash: from_address_hash,
input: data,
gas: gas,
gas_price: gas_price,
value: value
} = transaction
) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
req =
EthereumJSONRPCTransaction.eth_call_request(
0,
block_number,
data,
to_address_hash,
from_address_hash,
Decimal.to_integer(gas),
Wei.hex_format(gas_price),
Wei.hex_format(value)
)
data =
case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do
{:error, %{data: data}} ->
data
_ ->
""
end
revert_reason_parts = String.split(data, "revert: ")
formatted_revert_reason =
if Enum.count(revert_reason_parts) > 1 do
Enum.at(revert_reason_parts, 1)
else
data
end
if byte_size(formatted_revert_reason) > 0 do
transaction
|> Changeset.change(%{revert_reason: formatted_revert_reason})
|> Repo.update()
end
formatted_revert_reason
end
@doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
@ -2716,6 +2806,42 @@ defmodule Explorer.Chain do
|> Data.to_string()
end
def smart_contract_creation_tx_bytecode(address_hash) do
creation_tx_query =
from(
tx in Transaction,
where: tx.created_contract_address_hash == ^address_hash,
select: tx.input
)
tx_input =
creation_tx_query
|> Repo.one()
if tx_input do
Data.to_string(tx_input)
else
creation_int_tx_query =
from(
itx in InternalTransaction,
join: t in assoc(itx, :transaction),
where: itx.created_contract_address_hash == ^address_hash,
where: t.status == ^1,
select: itx.init
)
itx_init_code =
creation_int_tx_query
|> Repo.one()
if itx_init_code do
Data.to_string(itx_init_code)
else
nil
end
end
end
@doc """
Checks if an address is a contract
"""
@ -2777,6 +2903,9 @@ defmodule Explorer.Chain do
transaction.contracts_creation_internal_transaction && transaction.contracts_creation_internal_transaction.input ->
Data.to_string(transaction.contracts_creation_internal_transaction.input)
transaction.contracts_creation_internal_transaction && transaction.contracts_creation_internal_transaction.init ->
Data.to_string(transaction.contracts_creation_internal_transaction.init)
transaction.contracts_creation_transaction && transaction.contracts_creation_transaction.input ->
Data.to_string(transaction.contracts_creation_transaction.input)
@ -3428,17 +3557,69 @@ defmodule Explorer.Chain do
def address_to_coin_balances(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> CoinBalance.fetch_coin_balances(paging_options)
|> page_coin_balances(paging_options)
|> Repo.all()
|> Enum.dedup_by(fn record ->
if record.delta == Decimal.new(0) do
:dup
else
System.unique_integer()
end
end)
balances_raw =
address_hash
|> CoinBalance.fetch_coin_balances(paging_options)
|> page_coin_balances(paging_options)
|> Repo.all()
if Enum.empty?(balances_raw) do
balances_raw
else
balances_raw_filtered =
balances_raw
|> Enum.filter(fn balance -> balance.value end)
min_block_number =
balances_raw_filtered
|> Enum.min_by(fn balance -> balance.block_number end, fn -> %{} end)
|> Map.get(:block_number)
max_block_number =
balances_raw_filtered
|> Enum.max_by(fn balance -> balance.block_number end, fn -> %{} end)
|> Map.get(:block_number)
min_block_timestamp = find_block_timestamp(min_block_number)
max_block_timestamp = find_block_timestamp(max_block_number)
min_block_unix_timestamp =
min_block_timestamp
|> Timex.to_unix()
max_block_unix_timestamp =
max_block_timestamp
|> Timex.to_unix()
blocks_delta = max_block_number - min_block_number
balances_with_dates =
if blocks_delta > 0 do
balances_raw_filtered
|> Enum.map(fn balance ->
date =
trunc(
min_block_unix_timestamp +
(balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) /
blocks_delta
)
formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date}
end)
else
balances_raw_filtered
|> Enum.map(fn balance ->
date = min_block_unix_timestamp
formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date}
end)
end
balances_with_dates
|> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end)
end
end
def get_coin_balance(address_hash, block_number) do
@ -3455,8 +3636,9 @@ defmodule Explorer.Chain do
|> Repo.one()
address_hash
|> CoinBalance.balances_by_day(latest_block_timestamp)
|> CoinBalanceDaily.balances_by_day()
|> Repo.all()
|> Enum.sort_by(fn %{date: d} -> {d.year, d.month, d.day} end)
|> replace_last_value(latest_block_timestamp)
|> normalize_balances_by_day()
end
@ -3471,7 +3653,6 @@ defmodule Explorer.Chain do
defp normalize_balances_by_day(balances_by_day) do
result =
balances_by_day
|> Enum.map(fn day -> Map.take(day, [:date, :value]) end)
|> Enum.filter(fn day -> day.value end)
|> Enum.map(fn day -> Map.update!(day, :date, &to_string(&1)) end)
|> Enum.map(fn day -> Map.update!(day, :value, &Wei.to(&1, :ether)) end)
@ -4269,4 +4450,11 @@ defmodule Explorer.Chain do
block_index
end
end
defp find_block_timestamp(number) do
Block
|> where([b], b.number == ^number)
|> select([b], b.timestamp)
|> Repo.one()
end
end

@ -72,16 +72,18 @@ defmodule Explorer.Chain.Address.CoinBalance do
The last coin balance from an Address is the last block indexed.
"""
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
from(
cb in CoinBalance,
where: cb.address_hash == ^address_hash,
where: not is_nil(cb.value),
inner_join: b in Block,
on: cb.block_number == b.number,
order_by: [desc: :block_number],
limit: ^page_size,
select_merge: %{delta: fragment("value - coalesce(lag(value, 1) over (order by block_number), 0)")},
select_merge: %{block_timestamp: b.timestamp}
query =
from(
cb in CoinBalance,
where: cb.address_hash == ^address_hash,
where: not is_nil(cb.value),
order_by: [desc: :block_number],
select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")}
)
from(balance in subquery(query),
where: balance.delta != 0,
limit: ^page_size
)
end
@ -90,29 +92,55 @@ defmodule Explorer.Chain.Address.CoinBalance do
corresponds to the maximum balance in that day. Only the last 90 days of data are used.
"""
def balances_by_day(address_hash, block_timestamp \\ nil) do
{days_to_consider, _} =
Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days]
|> Integer.parse()
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
|> limit_time_interval(block_timestamp)
|> limit_time_interval(days_to_consider, block_timestamp)
|> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end
def limit_time_interval(query, nil) do
query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'"))
def limit_time_interval(query, days_to_consider, nil) do
query
|> where(
[cb, b],
b.timestamp >=
fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end
def limit_time_interval(query, %{timestamp: timestamp}) do
query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC"))
def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
query
|> where(
[cb, b],
b.timestamp >=
fragment(
"(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
^timestamp,
^"Etc/UTC",
^%Postgrex.Interval{days: days_to_consider}
)
)
end
def last_coin_balance_timestamp(address_hash) do
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
|> last(:block_number)
|> select([cb, b], %{timestamp: b.timestamp, value: cb.value})
coin_balance_query =
CoinBalance
|> where([cb], cb.address_hash == ^address_hash)
|> last(:block_number)
|> select([cb, b], %{block_number: cb.block_number, value: cb.value})
from(
cb in subquery(coin_balance_query),
inner_join: b in Block,
on: cb.block_number == b.number,
select: %{timestamp: b.timestamp, value: cb.value}
)
end
def changeset(%__MODULE__{} = balance, params) do

@ -0,0 +1,74 @@
defmodule Explorer.Chain.Address.CoinBalanceDaily do
@moduledoc """
Maximum `t:Explorer.Chain.Wei.t/0` `value` of `t:Explorer.Chain.Address.t/0` at the day.
This table is used to display coinn balance history chart.
"""
use Explorer.Schema
alias Explorer.Chain.{Address, Hash, Wei}
alias Explorer.Chain.Address.CoinBalanceDaily
@optional_fields ~w(value)a
@required_fields ~w(address_hash day)a
@allowed_fields @optional_fields ++ @required_fields
@typedoc """
* `address` - the `t:Explorer.Chain.Address.t/0`.
* `address_hash` - foreign key for `address`.
* `day` - the `t:Date.t/0`.
* `inserted_at` - When the balance was first inserted into the database.
* `updated_at` - When the balance was last updated.
* `value` - the max balance (`value`) of `address` during the `day`.
"""
@type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(),
address_hash: Hash.Address.t(),
day: Date.t(),
inserted_at: DateTime.t(),
updated_at: DateTime.t(),
value: Wei.t() | nil
}
@primary_key false
schema "address_coin_balances_daily" do
field(:day, :date)
field(:value, Wei)
timestamps()
belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address)
end
@doc """
Builds an `Ecto.Query` to fetch a series of balances by day for the given account. Each element in the series
corresponds to the maximum balance in that day. Only the last `n` days of data are used.
`n` is configurable via COIN_BALANCE_HISTORY_DAYS ENV var.
"""
def balances_by_day(address_hash) do
{days_to_consider, _} =
Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days]
|> Integer.parse()
CoinBalanceDaily
|> where([cbd], cbd.address_hash == ^address_hash)
|> limit_time_interval(days_to_consider)
|> select([cbd], %{date: cbd.day, value: cbd.value})
end
def limit_time_interval(query, days_to_consider) do
query
|> where(
[cbd],
cbd.day >= fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end
def changeset(%__MODULE__{} = balance, params) do
balance
|> cast(params, @allowed_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:address_hash)
|> unique_constraint(:day, name: :address_coin_balances_daily_address_hash_day_index)
end
end

@ -31,6 +31,13 @@ defmodule Explorer.Chain.Cache.AddressSumMinusBurnt do
try do
result = Chain.fetch_sum_coin_total_supply_minus_burnt()
params = %{
counter_type: "sum_coin_total_supply_minus_burnt",
value: result
}
Chain.upsert_last_fetched_counter(params)
set_sum_minus_burnt(result)
rescue
e ->

@ -1,42 +0,0 @@
defmodule Explorer.Chain.Cache.PendingTransactions do
@moduledoc """
Caches the latest pending transactions
"""
alias Explorer.Chain.Transaction
use Explorer.Chain.OrderedCache,
name: :pending_transactions,
max_size: 51,
preloads: [
:block,
created_contract_address: :names,
from_address: :names,
to_address: :names,
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Transaction.t()
@type id :: {non_neg_integer(), non_neg_integer()}
def element_to_id(%Transaction{inserted_at: inserted_at, hash: hash}) do
{inserted_at, hash}
end
def update_pending(transactions) when is_nil(transactions), do: :ok
def update_pending(transactions) do
transactions
|> Enum.filter(&pending?(&1))
|> update()
end
defp pending?(transaction) do
is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced")
end
end

@ -0,0 +1,139 @@
defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do
@moduledoc """
Bulk imports `t:Explorer.Chain.Address.CoinBalancesDaily.t/0`.
"""
require Ecto.Query
import Ecto.Query, only: [from: 2]
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.Address.CoinBalanceDaily
alias Explorer.Chain.{Hash, Import, Wei}
@behaviour Import.Runner
# milliseconds
@timeout 60_000
@type imported :: [
%{required(:address_hash) => Hash.Address.t(), required(:day) => Date.t()}
]
@impl Import.Runner
def ecto_schema_module, do: CoinBalanceDaily
@impl Import.Runner
def option_key, do: :address_coin_balances_daily
@impl Import.Runner
def imported_table_row do
%{
value_type: "[%{address_hash: Explorer.Chain.Hash.t(), day: Date.t()}]",
value_description: "List of maps of the `t:#{ecto_schema_module()}.t/0` `address_hash` and `day`"
}
end
@impl Import.Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) do
insert_options =
options
|> Map.get(option_key(), %{})
|> Map.take(~w(on_conflict timeout)a)
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)
Multi.run(multi, :address_coin_balances_daily, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
end
@impl Import.Runner
def timeout, do: @timeout
@spec insert(
Repo.t(),
[
%{
required(:address_hash) => Hash.Address.t(),
required(:day) => Date.t(),
required(:value) => Wei.t()
}
],
%{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}
) ::
{:ok, [%{required(:address_hash) => Hash.Address.t(), required(:day) => Date.t()}]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
combined_changes_list =
changes_list
|> Enum.reduce([], fn change, acc ->
if Enum.empty?(acc) do
[change | acc]
else
target_item =
Enum.find(acc, fn item ->
item.day == change.day && item.address_hash == change.address_hash
end)
if target_item do
if change.value > target_item.value do
acc_updated = List.delete(acc, target_item)
[change | acc_updated]
else
acc
end
else
[change | acc]
end
end
end)
# Enforce CoinBalanceDaily ShareLocks order (see docs: sharelocks.md)
ordered_changes_list = Enum.sort_by(combined_changes_list, &{&1.address_hash, &1.day})
{:ok, _} =
Import.insert_changes_list(
repo,
ordered_changes_list,
conflict_target: [:address_hash, :day],
on_conflict: on_conflict,
for: CoinBalanceDaily,
timeout: timeout,
timestamps: timestamps
)
{:ok, Enum.map(ordered_changes_list, &Map.take(&1, ~w(address_hash day)a))}
end
def default_on_conflict do
from(
balance in CoinBalanceDaily,
update: [
set: [
value:
fragment(
"""
CASE WHEN EXCLUDED.value IS NOT NULL AND EXCLUDED.value > ? THEN
EXCLUDED.value
ELSE
?
END
""",
balance.value,
balance.value
),
inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", balance.inserted_at),
updated_at: fragment("GREATEST(EXCLUDED.updated_at, ?)", balance.updated_at)
]
],
where: fragment("EXCLUDED.value IS NOT NULL")
)
end
end

@ -14,7 +14,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do
Runner.Address.CoinBalances,
Runner.Blocks,
Runner.StakingPools,
Runner.StakingPoolsDelegators
Runner.StakingPoolsDelegators,
Runner.Address.CoinBalancesDaily
]
@impl Stage

@ -326,6 +326,7 @@ defmodule Explorer.Chain.SmartContract do
defp upsert_contract_methods(changeset), do: changeset
defp error_message(:compilation), do: "There was an error compiling your contract."
defp error_message(:compiler_version), do: "Compiler version does not match, please try again."
defp error_message(:generated_bytecode), do: "Bytecode does not match, please try again."
defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again."
defp error_message(:name), do: "Wrong contract name, please try again."

@ -281,6 +281,7 @@ defmodule Explorer.Chain.TokenTransfer do
end
@doc """
Innventory tab query.
A token ERC-721 is considered unique because it corresponds to the possession
of a specific asset.
@ -293,9 +294,9 @@ defmodule Explorer.Chain.TokenTransfer do
tt in TokenTransfer,
left_join: instance in Instance,
on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id,
where: tt.token_contract_address_hash == ^contract_address_hash,
where: instance.token_contract_address_hash == ^contract_address_hash,
order_by: [desc: tt.block_number],
distinct: tt.token_id,
distinct: [desc: tt.token_id],
preload: [:to_address],
select: %{tt | instance: instance}
)

@ -30,7 +30,7 @@ defmodule Explorer.Chain.Transaction do
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index created_contract_code_indexed_at status
to_address_hash)a
to_address_hash revert_reason)a
@required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
@ -106,6 +106,7 @@ defmodule Explorer.Chain.Transaction do
* `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer`
* `revert_reason` - revert reason of transaction
| `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description |
|----------|----------------------------------|------------|-----------------|-------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------|
@ -129,6 +130,7 @@ defmodule Explorer.Chain.Transaction do
* `uncles` - uncle blocks where `forks` were collated
* `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address`
* `revert_reason` - revert reason of transaction
"""
@type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
@ -159,7 +161,8 @@ defmodule Explorer.Chain.Transaction do
to_address_hash: Hash.Address.t() | nil,
uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
v: v(),
value: Wei.t()
value: Wei.t(),
revert_reason: String.t()
}
@derive {Poison.Encoder,
@ -199,6 +202,7 @@ defmodule Explorer.Chain.Transaction do
field(:status, Status)
field(:v, :decimal)
field(:value, Wei)
field(:revert_reason, :string)
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated

@ -5,6 +5,7 @@ defmodule Explorer.ChainSpec.Parity.Importer do
require Logger
alias EthereumJSONRPC.Blocks
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash
@ -33,6 +34,21 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end)
|> Enum.to_list()
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(1..1, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
balance_daily_params =
chain_spec
|> genesis_accounts()
|> Stream.map(fn balance_map ->
Map.put(balance_map, :day, day)
end)
|> Enum.to_list()
address_params =
balance_params
|> Stream.map(fn %{address_hash: hash} = map ->
@ -40,7 +56,11 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end)
|> Enum.to_list()
params = %{address_coin_balances: %{params: balance_params}, addresses: %{params: address_params}}
params = %{
address_coin_balances: %{params: balance_params},
address_coin_balances_daily: %{params: balance_daily_params},
addresses: %{params: address_params}
}
Chain.import(params)
end

@ -0,0 +1,30 @@
defmodule Explorer.Counters.LastFetchedCounter do
@moduledoc """
Stores last fetched counters.
"""
alias Explorer.Counters.LastFetchedCounter
use Explorer.Schema
import Ecto.Changeset
@type t :: %LastFetchedCounter{
counter_type: String.t(),
value: Decimal.t()
}
@primary_key false
schema "last_fetched_counters" do
field(:counter_type, :string)
field(:value, :decimal)
timestamps()
end
@doc false
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:counter_type, :value])
|> validate_required([:counter_type])
end
end

@ -332,6 +332,7 @@ defmodule Explorer.Etherscan do
status
to_address_hash
value
revert_reason
)a
defp list_transactions(address_hash, max_block_number, options) do

@ -59,7 +59,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
"type" => "function"
}
],
"bytecode" => "6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820834bdab406d80509618957aa1a5ad1a4b77f4f1149078675940494ebe5b4147b0029",
"bytecode" => "608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820834bdab406d80509618957aa1a5ad1a4b77f4f1149078675940494ebe5b4147b0029",
"name" => "SimpleStorage"
}
}
@ -92,19 +92,18 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
[
Application.app_dir(:explorer, "priv/compile_solc.js"),
create_source_file(code),
compiler_version,
path,
optimize_value(optimize),
optimization_runs,
@new_contract_name,
external_libs_string,
checked_evm_version,
path
checked_evm_version
]
)
with {:ok, decoded} <- Jason.decode(response),
{:ok, contracts} <- get_contracts(decoded),
%{"abi" => abi, "evm" => %{"deployedBytecode" => %{"object" => bytecode}}} <-
%{"abi" => abi, "evm" => %{"bytecode" => %{"object" => bytecode}}} <-
get_contract_info(contracts, name) do
{:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}}
else

@ -1,3 +1,4 @@
# credo:disable-for-this-file
defmodule Explorer.SmartContract.Verifier do
@moduledoc """
Module responsible to verify the Smart Contract.
@ -11,6 +12,13 @@ defmodule Explorer.SmartContract.Verifier do
alias Explorer.SmartContract.Solidity.CodeCompiler
alias Explorer.SmartContract.Verifier.ConstructorArguments
@metadata_hash_prefix_0_4_23 "a165627a7a72305820"
@metadata_hash_prefix_0_5_10 "a265627a7a72305820"
@metadata_hash_prefix_0_5_11 "a265627a7a72315820"
@metadata_hash_prefix_0_6_0 "a264697066735822"
@metadata_hash_common_suffix "64736f6c63"
def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name}
def evaluate_authenticity(_, %{"contract_source_code" => ""}),
@ -76,16 +84,28 @@ defmodule Explorer.SmartContract.Verifier do
contract_source_code,
contract_name
) do
generated_bytecode = extract_bytecode(bytecode)
%{
"metadata_hash" => _generated_metadata_hash,
"bytecode" => generated_bytecode,
"compiler_version" => generated_compiler_version
} = extract_bytecode_and_metadata_hash(bytecode)
"0x" <> blockchain_bytecode =
"0x" <> blockchain_created_tx_input =
address_hash
|> Chain.smart_contract_bytecode()
|> Chain.smart_contract_creation_tx_bytecode()
%{
"metadata_hash" => _metadata_hash,
"bytecode" => blockchain_bytecode_without_whisper,
"compiler_version" => compiler_version
} = extract_bytecode_and_metadata_hash(blockchain_created_tx_input)
blockchain_bytecode_without_whisper = extract_bytecode(blockchain_bytecode)
empty_constructor_arguments = arguments_data == "" or arguments_data == nil
cond do
generated_compiler_version != compiler_version ->
{:error, :compiler_version}
generated_bytecode != blockchain_bytecode_without_whisper &&
!try_library_verification(generated_bytecode, blockchain_bytecode_without_whisper) ->
{:error, :generated_bytecode}
@ -136,40 +156,87 @@ defmodule Explorer.SmartContract.Verifier do
For more information on the swarm hash, check out:
https://solidity.readthedocs.io/en/v0.5.3/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
"""
def extract_bytecode("0x" <> code) do
"0x" <> extract_bytecode(code)
def extract_bytecode_and_metadata_hash("0x" <> code) do
%{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version} =
extract_bytecode_and_metadata_hash(code)
%{"metadata_hash" => metadata_hash, "bytecode" => "0x" <> bytecode, "compiler_version" => compiler_version}
end
def extract_bytecode(code) do
do_extract_bytecode([], String.downcase(code))
def extract_bytecode_and_metadata_hash(code) do
do_extract_bytecode_and_metadata_hash([], String.downcase(code), nil, nil)
end
defp do_extract_bytecode(extracted, remaining) do
defp do_extract_bytecode_and_metadata_hash(extracted, remaining, metadata_hash, compiler_version) do
case remaining do
<<>> ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
"a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> _constructor_arguments ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
@metadata_hash_prefix_0_4_23 <> <<metadata_hash::binary-size(64)>> <> "0029" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
# Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst
"a265627a7a72305820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
@metadata_hash_prefix_0_5_10 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"43" <> <<compiler_version::binary-size(6)>> <> "0032" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_10 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7826" <> <<compiler_version::binary-size(76)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_10 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7827" <> <<compiler_version::binary-size(78)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_10 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7828" <> <<compiler_version::binary-size(80)>> <> "0058" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_10 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7829" <> <<compiler_version::binary-size(82)>> <> "0059" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
# Solidity >= 0.5.11 https://github.com/ethereum/solidity/blob/develop/Changelog.md#0511-2019-08-12
# Metadata: Update the swarm hash to the current specification, changes bzzr0 to bzzr1 and urls to use bzz-raw://
"a265627a7a72315820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
@metadata_hash_prefix_0_5_11 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"43" <> <<compiler_version::binary-size(6)>> <> "0032" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_11 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7826" <> <<compiler_version::binary-size(76)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_11 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7827" <> <<compiler_version::binary-size(78)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_11 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7828" <> <<compiler_version::binary-size(80)>> <> "0058" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_5_11 <>
<<metadata_hash::binary-size(64)>> <>
@metadata_hash_common_suffix <>
"7829" <> <<compiler_version::binary-size(82)>> <> "0059" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
# Solidity >= 0.6.0 https://github.com/ethereum/solidity/blob/develop/Changelog.md#060-2019-12-17
# https://github.com/ethereum/solidity/blob/26b700771e9cc9c956f0503a05de69a1be427963/docs/metadata.rst#encoding-of-the-metadata-hash-in-the-bytecode
@ -181,17 +248,50 @@ defmodule Explorer.SmartContract.Verifier do
# 0x00 0x32
# Note: there is a bug in the docs. Instead of 0x32, 0x33 should be used.
# Fixing PR has been created https://github.com/ethereum/solidity/pull/8174
"a264697066735822" <>
<<_::binary-size(68)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0033" <> _constructor_arguments ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
@metadata_hash_prefix_0_6_0 <>
<<metadata_hash::binary-size(68)>> <>
@metadata_hash_common_suffix <>
"43" <> <<compiler_version::binary-size(6)>> <> "0033" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_6_0 <>
<<metadata_hash::binary-size(68)>> <>
@metadata_hash_common_suffix <>
"7826" <> <<compiler_version::binary-size(76)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_6_0 <>
<<metadata_hash::binary-size(68)>> <>
@metadata_hash_common_suffix <>
"7827" <> <<compiler_version::binary-size(78)>> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_6_0 <>
<<metadata_hash::binary-size(68)>> <>
@metadata_hash_common_suffix <>
"7828" <> <<compiler_version::binary-size(80)>> <> "0058" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
@metadata_hash_prefix_0_6_0 <>
<<metadata_hash::binary-size(68)>> <>
@metadata_hash_common_suffix <>
"7829" <> <<compiler_version::binary-size(82)>> <> "0059" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
<<next::binary-size(2)>> <> rest ->
do_extract_bytecode([next | extracted], rest)
do_extract_bytecode_and_metadata_hash([next | extracted], rest, metadata_hash, compiler_version)
end
end
defp do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) do
bytecode =
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
%{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version}
end
def previous_evm_versions(current_evm_version) do
index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end)

@ -1,3 +1,4 @@
# credo:disable-for-this-file
defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@moduledoc """
Smart contract contrstructor arguments verification logic.
@ -5,6 +6,13 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain
@metadata_hash_prefix_0_4_23 "a165627a7a72305820"
@metadata_hash_prefix_0_5_10 "a265627a7a72305820"
@metadata_hash_prefix_0_5_11 "a265627a7a72315820"
@metadata_hash_prefix_0_6_0 "a264697066735822"
@metadata_hash_common_suffix "64736f6c63"
def verify(address_hash, contract_code, arguments_data, contract_source_code, contract_name) do
arguments_data = arguments_data |> String.trim_trailing() |> String.trim_leading("0x")
@ -34,19 +42,127 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
defp extract_constructor_arguments(code, check_func, contract_source_code, contract_name) do
case code do
# Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html
"a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name)
@metadata_hash_prefix_0_4_23 <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_4_23
)
# Solidity >= 0.5.10 https://solidity.readthedocs.io/en/v0.5.10/metadata.html
"a265627a7a72305820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name)
@metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_10
)
@metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_10
)
@metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_10
)
@metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_10
)
@metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_10
)
# Solidity >= 0.5.11 https://github.com/ethereum/solidity/blob/develop/Changelog.md#0511-2019-08-12
# Metadata: Update the swarm hash to the current specification, changes bzzr0 to bzzr1 and urls to use bzz-raw://
"a265627a7a72315820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name)
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_11
)
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_11
)
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_11
)
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_11
)
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_5_11
)
# Solidity >= 0.6.0 https://github.com/ethereum/solidity/blob/develop/Changelog.md#060-2019-12-17
# https://github.com/ethereum/solidity/blob/26b700771e9cc9c956f0503a05de69a1be427963/docs/metadata.rst#encoding-of-the-metadata-hash-in-the-bytecode
@ -58,9 +174,60 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
# 0x00 0x32
# Note: there is a bug in the docs. Instead of 0x32, 0x33 should be used.
# Fixing PR has been created https://github.com/ethereum/solidity/pull/8174
"a264697066735822" <>
<<_::binary-size(68)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0033" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name)
@metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <>
@metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0033" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_6_0
)
@metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <>
@metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_6_0
)
@metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <>
@metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_6_0
)
@metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <>
@metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_6_0
)
@metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <>
@metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
@metadata_hash_prefix_0_6_0
)
<<>> ->
check_func.("")
@ -70,6 +237,35 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
end
end
defp is_constructor_arguments_still_has_metadata_prefix(constructor_arguments, prefix) do
constructor_arguments_parts =
constructor_arguments
|> String.split(prefix)
|> Enum.count()
constructor_arguments_parts > 1
end
defp split_constructor_arguments_and_extract_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name,
metadata_hash_prefix
) do
if is_constructor_arguments_still_has_metadata_prefix(constructor_arguments, metadata_hash_prefix) do
<<_::binary-size(2)>> <> rest = constructor_arguments
extract_constructor_arguments(rest, check_func, contract_source_code, contract_name)
else
extract_constructor_arguments_check_func(
constructor_arguments,
check_func,
contract_source_code,
contract_name
)
end
end
defp extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) do
constructor_arguments =
remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name)
@ -195,11 +391,17 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
def find_all_requires(contract_source_code) do
if contract_source_code do
[_ | requires] = String.split(contract_source_code, "require")
trimmed_source_code =
contract_source_code
|> String.replace(~r/ +/, " ")
|> String.replace("require(", "require (")
[_ | requires] =
trimmed_source_code
|> String.split("require (")
Enum.reduce(requires, [], fn right_from_require, requires_list ->
[_ | [right_from_require_inside]] = String.split(right_from_require, "(", parts: 2)
[require_content | _] = String.split(right_from_require_inside, ");", parts: 2)
[require_content | _] = String.split(right_from_require, ");", parts: 2)
[require_content | requires_list]
end)
else

@ -98,10 +98,16 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
defp fetch_metadata(token_uri) do
case HTTPoison.get(token_uri) do
{:ok, %Response{body: body, status_code: 200}} ->
{:ok, json} = decode_json(body)
{:ok, %Response{body: body, status_code: 200, headers: headers}} ->
if Enum.member?(headers, {"Content-Type", "image/png"}) do
json = %{"image" => token_uri}
check_type(json)
check_type(json)
else
{:ok, json} = decode_json(body)
check_type(json)
end
{:ok, %Response{body: body}} ->
{:error, body}

@ -84,7 +84,7 @@ defmodule Explorer.Mixfile do
# Code coverage
{:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"},
{:exvcr, "~> 0.10", only: :test},
{:httpoison, "~> 1.0"},
{:httpoison, "~> 1.6"},
{:jason, "~> 1.0"},
{:junit_formatter, ">= 0.0.0", only: [:test], runtime: false},
# Log errors and application output to separate files

@ -8,14 +8,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -26,9 +18,9 @@
}
},
"command-exists": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz",
"integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw=="
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
},
"commander": {
"version": "3.0.2",
@ -40,11 +32,6 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"fs-extra": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
@ -76,9 +63,9 @@
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"inflight": {
"version": "1.0.6",
@ -107,17 +94,6 @@
"graceful-fs": "^4.1.6"
}
},
"keccak": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-2.0.0.tgz",
"integrity": "sha512-rKe/lRr0KGhjoz97cwg+oeT1Rj/Y4cjae6glArioUC8JBF9ROGZctwIaaruM7d7naovME4Q8WcQSO908A8qcyQ==",
"requires": {
"bindings": "^1.2.1",
"inherits": "^2.0.3",
"nan": "^2.2.1",
"safe-buffer": "^5.1.0"
}
},
"klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
@ -139,11 +115,6 @@
"brace-expansion": "^1.1.7"
}
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -175,20 +146,15 @@
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"solc": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.6.1.tgz",
"integrity": "sha512-iKqNYps2p++x8L9sBg7JeAJb7EmW8VJ/2asAzwlLYcUhj86AzuWLe94UTSQHv1SSCCj/x6lya8twvXkZtlTbIQ==",
"version": "0.6.7",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.6.7.tgz",
"integrity": "sha512-a3iocjS1yGzw3Wy7jkqSLX3Vg1lMDCyoKZoVfpOagRGWkh37f11BrcUDO8f73rjdpw2WUBSLJtTQ26i52/0JOg==",
"requires": {
"command-exists": "^1.2.8",
"commander": "3.0.2",

@ -13,7 +13,6 @@
},
"scripts": {},
"dependencies": {
"keccak": "^2.0.0",
"solc": "^0.6.1"
"solc": "^0.6.7"
}
}

@ -1,13 +1,12 @@
#!/usr/bin/env node
var sourceCodePath = process.argv[2];
var version = process.argv[3];
var compilerVersionPath = process.argv[3];
var optimize = process.argv[4];
var optimizationRuns = parseInt(process.argv[5], 10);
var newContractName = process.argv[6];
var externalLibraries = JSON.parse(process.argv[7])
var evmVersion = process.argv[8];
var compilerVersionPath = process.argv[9];
var solc = require('solc')
var compilerSnapshot = require(compilerVersionPath);

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.CreateIndexBlocksMinerHashNumberIndex do
use Ecto.Migration
def change do
create_if_not_exists(index(:blocks, [:miner_hash, :number]))
end
end

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.CreateTokenTransfersTokenContractAddressHashTokenIdBlockNumberIndex do
use Ecto.Migration
def change do
create_if_not_exists(index(:token_transfers, [:token_contract_address_hash, "token_id DESC", "block_number DESC"]))
end
end

@ -0,0 +1,17 @@
defmodule Explorer.Repo.Migrations.AddressCoinBalancesDaily do
use Ecto.Migration
def change do
create table(:address_coin_balances_daily, primary_key: false) do
add(:address_hash, references(:addresses, column: :hash, type: :bytea), null: false)
add(:day, :date, null: false)
# null until fetched
add(:value, :numeric, precision: 100, default: fragment("NULL"), null: true)
timestamps(null: false, type: :utc_datetime_usec)
end
create(unique_index(:address_coin_balances_daily, [:address_hash, :day]))
end
end

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.AddCountersTable do
use Ecto.Migration
def change do
create table(:last_fetched_counters, primary_key: false) do
add(:counter_type, :string, primary_key: true, null: false)
add(:value, :numeric, precision: 100, null: true)
timestamps(null: false, type: :utc_datetime_usec)
end
end
end

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.AlterTransactionsAddErrorReason do
use Ecto.Migration
def change do
alter table(:transactions) do
add(:revert_reason, :text)
end
end
end

@ -93,7 +93,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do
value_fetched_at: DateTime.utc_now()
}
run_changes(new_changes, options) |> IO.inspect()
run_changes(new_changes, options)
end
end

@ -156,6 +156,12 @@ defmodule Explorer.Chain.TokenTransferTest do
|> insert()
|> with_block(insert(:block, number: 1))
insert(
:token_instance,
token_id: 42,
token_contract_address_hash: token_contract_address.hash
)
insert(
:token_transfer,
to_address: build(:address),
@ -168,7 +174,7 @@ defmodule Explorer.Chain.TokenTransferTest do
another_transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 2))
|> with_block(insert(:block, number: 3))
last_owner =
insert(

File diff suppressed because one or more lines are too long

@ -4719,6 +4719,8 @@ defmodule Explorer.ChainTest do
block_one_day_ago = insert(:block, timestamp: yesterday, number: 49)
insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block.number)
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: noon)
insert(:fetched_balance_daily, address_hash: address.hash, value: 2000, day: yesterday)
balances = Chain.address_to_balances_by_day(address.hash)
@ -4735,6 +4737,7 @@ defmodule Explorer.ChainTest do
yesterday = Timex.shift(noon, days: -1)
block_one_day_ago = insert(:block, timestamp: yesterday)
insert(:fetched_balance, address_hash: address.hash, value: 1000, block_number: block_one_day_ago.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 1000, day: yesterday)
balances = Chain.address_to_balances_by_day(address.hash)
@ -4754,6 +4757,7 @@ defmodule Explorer.ChainTest do
block_past = insert(:block, timestamp: past, number: 2)
insert(:fetched_balance, address_hash: address.hash, value: 0, block_number: block_past.number)
insert(:fetched_balance_daily, address_hash: address.hash, value: 0, day: today)
[balance] = Chain.address_to_balances_by_day(address.hash)
@ -5021,32 +5025,6 @@ defmodule Explorer.ChainTest do
end
end
describe "address_to_coin_balances/2" do
test "deduplicates records by zero delta" do
address = insert(:address)
1..5
|> Enum.each(fn block_number ->
insert(:block, number: block_number)
insert(:fetched_balance, value: 1, block_number: block_number, address_hash: address.hash)
end)
insert(:block, number: 6)
insert(:fetched_balance, value: 2, block_number: 6, address_hash: address.hash)
assert [first, second, third] = Chain.address_to_coin_balances(address.hash, [])
assert first.block_number == 6
assert first.delta == Decimal.new(1)
assert second.block_number == 5
assert second.delta == Decimal.new(0)
assert third.block_number == 1
assert third.delta == Decimal.new(1)
end
end
describe "extract_db_name/1" do
test "extracts correct db name" do
db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1"
@ -5187,4 +5165,32 @@ defmodule Explorer.ChainTest do
}
end
end
describe "transaction_to_revert_reason/1" do
test "returns correct revert_reason from DB" do
transaction = insert(:transaction, revert_reason: "No credit of that type")
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end
test "returns correct revert_reason from the archive node" do
transaction =
insert(:transaction,
gas: 27319,
gas_price: "0x1b31d2900",
value: "0x86b3",
input: %Explorer.Chain.Data{bytes: <<1>>}
)
|> with_block(insert(:block, number: 1))
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _json, [] ->
{:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
end
)
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end
end
end

File diff suppressed because one or more lines are too long

@ -92,9 +92,9 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
)
clean_result = remove_init_data_and_whisper_data(result["bytecode"])
expected_result = remove_init_data_and_whisper_data(compiler_test["expected_bytecode"])
expected_result = remove_init_data_and_whisper_data(compiler_test["tx_input"])
assert clean_result == expected_result
assert expected_result == clean_result
end)
end
@ -166,7 +166,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
}
"""
version = "v0.1.3-nightly.2015.9.25+commit.4457170"
version = "v0.1.3+commit.028f561d"
response = CodeCompiler.run(name: name, compiler_version: version, code: code, optimize: optimize)

File diff suppressed because one or more lines are too long

@ -16,6 +16,7 @@ defmodule Explorer.Factory do
Address.CurrentTokenBalance,
Address.TokenBalance,
Address.CoinBalance,
Address.CoinBalanceDaily,
Block,
ContractMethod,
Data,
@ -56,6 +57,13 @@ defmodule Explorer.Factory do
}
end
def unfetched_balance_daily_factory do
%CoinBalanceDaily{
address_hash: address_hash(),
day: Timex.shift(Timex.now(), days: Enum.random(0..100) * -1)
}
end
def update_balance_value(%CoinBalance{address_hash: address_hash, block_number: block_number}, value) do
Repo.update_all(
from(
@ -71,6 +79,11 @@ defmodule Explorer.Factory do
|> struct!(value: Enum.random(1..100_000))
end
def fetched_balance_daily_factory do
unfetched_balance_daily_factory()
|> struct!(value: Enum.random(1..100_000))
end
def contract_address_factory do
%Address{
hash: address_hash(),
@ -82,6 +95,8 @@ defmodule Explorer.Factory do
%{
bytecode:
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820f65a3adc1cfb055013d1dc37d0fe98676e2a5963677fa7541a10386d163446680029",
tx_input:
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820853a985d0a4b20246785fc2f0357c202faa3db289980a48737180f358f9ddc3c0029",
name: "SimpleStorage",
source_code: """
pragma solidity ^0.4.24;

File diff suppressed because one or more lines are too long

@ -0,0 +1,18 @@
pragma solidity 0.4.26;
contract Factory {
address[] newContracts;
function createContract (bytes32 name) public {
address newContract = new ContractFromFactory(name);
newContracts.push(newContract);
}
}
contract ContractFromFactory {
bytes32 public Name;
constructor(bytes32 name) public {
Name = name;
}
}

@ -3,6 +3,7 @@
"compiler_version": "v0.5.11+commit.c082d0b4",
"contract": "pragma solidity 0.5.11;library BadSafeMath { function add(uint256 a, uint256 b) public pure returns (uint256) { uint256 c = a + 2 * b; require(c >= a, \"SafeMath: addition overflow\"); return c; }}contract SimpleStorage { uint256 storedData = 10; using BadSafeMath for uint256; function increment(uint256 x) public { storedData = storedData.add(x); } function set(uint256 x) public { storedData = x; } function get() public view returns (uint256) { return storedData; }}",
"expected_bytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158203e59bfb9a5a2e55d38231922c86d8b2ec9b66cb2f6595613674bc4e15290b60764736f6c634300050b0032",
"tx_input": "6080604052600a60005534801561001557600080fd5b50610169806100256000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158207809bc828bbcd3de3e6b6483facb0c5902901fc8827283c749c8ea0702eb871f64736f6c634300050b0032",
"external_libraries": {
"BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73"
},
@ -10,4 +11,3 @@
"optimize": true
}
]

@ -0,0 +1,25 @@
pragma solidity 0.5.11;
library BadSafeMath {
function add(uint256 a, uint256 b) public pure returns (uint256) {
uint256 c = a + 2 * b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
}
contract SimpleStorage {
uint256 storedData = 10;
using BadSafeMath for uint256;
function increment(uint256 x) public {
storedData = storedData.add(x);
}
function set(uint256 x) public {
storedData = x;
}
function get() public view returns (uint256) {
return storedData;
}
}

@ -0,0 +1,412 @@
/** *Submitted for verification at Etherscan.io on 2019-08-16
*/
pragma solidity ^0.5.0;
// ----------------------------------------------------------------------------
// 'FIXED' 'Example Fixed Supply Token' token contract
//
// Symbol : FIXED
// Name : Example Fixed Supply Token
// Total supply: 1,000,000.000000000000000000
// Decimals : 18
//
// Enjoy.
//
// (c) BokkyPooBah / Bok Consulting Pty Ltd 2018. The MIT Licence.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Safe maths
// ----------------------------------------------------------------------------
library SafeMath {
function add(uint a, uint b) internal pure returns (uint c) {
c = a + b;
require(c >= a);
}
function sub(uint a, uint b) internal pure returns (uint c) {
require(b <= a);
c = a - b;
}
function mul(uint a, uint b) internal pure returns (uint c) {
c = a * b;
require(a == 0 || c / a == b);
}
function div(uint a, uint b) internal pure returns (uint c) {
require(b > 0);
c = a / b;
}
}
// ----------------------------------------------------------------------------
// ERC Token Standard #20 Interface
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
// ----------------------------------------------------------------------------
contract ERC20Interface {
function totalSupply() public view returns (uint);
function balanceOf(address tokenOwner) public view returns (uint balance);
function allowance(address tokenOwner, address spender) public view returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
// ----------------------------------------------------------------------------
// Contract function to receive approval and execute function in one call
//
// Borrowed from MiniMeToken
// ----------------------------------------------------------------------------
contract ApproveAndCallFallBack {
function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public;
}
// ----------------------------------------------------------------------------
// Owned contract
// ----------------------------------------------------------------------------
contract Owned {
address public owner;
address public newOwner;
event OwnershipTransferred(address indexed _from, address indexed _to);
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address _newOwner) public onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() public {
require(msg.sender == newOwner);
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// ----------------------------------------------------------------------------
// ERC20 Token, with the addition of symbol, name and decimals and a
// fixed supply
// ----------------------------------------------------------------------------
contract FixedSupplyToken is ERC20Interface, Owned {
using SafeMath for uint;
string public symbol;
string public name;
uint8 public decimals;
uint _totalSupply;
mapping(address => uint) balances;
mapping(address => mapping(address => uint)) allowed;
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
constructor() public {
symbol = "TEST";
name = "Test Token";
decimals = 18;
_totalSupply = 1000000 * 10**uint(decimals);
balances[owner] = _totalSupply;
emit Transfer(address(0), owner, _totalSupply);
}
// ------------------------------------------------------------------------
// Total supply
// ------------------------------------------------------------------------
function totalSupply() public view returns (uint) {
return _totalSupply.sub(balances[address(0)]);
}
// ------------------------------------------------------------------------
// Get the token balance for account `tokenOwner`
// ------------------------------------------------------------------------
function balanceOf(address tokenOwner) public view returns (uint balance) {
return balances[tokenOwner];
}
// ------------------------------------------------------------------------
// Transfer the balance from token owner's account to `to` account
// - Owner's account must have sufficient balance to transfer
// - 0 value transfers are allowed
// ------------------------------------------------------------------------
function transfer(address to, uint tokens) public returns (bool success) {
balances[msg.sender] = balances[msg.sender].sub(tokens);
balances[to] = balances[to].add(tokens);
emit Transfer(msg.sender, to, tokens);
return true;
}
// ------------------------------------------------------------------------
// Token owner can approve for `spender` to transferFrom(...) `tokens`
// from the token owner's account
//
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
// recommends that there are no checks for the approval double-spend attack
// as this should be implemented in user interfaces
// ------------------------------------------------------------------------
function approve(address spender, uint tokens) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
// ------------------------------------------------------------------------
// Transfer `tokens` from the `from` account to the `to` account
//
// The calling account must already have sufficient tokens approve(...)-d
// for spending from the `from` account and
// - From account must have sufficient balance to transfer
// - Spender must have sufficient allowance to transfer
// - 0 value transfers are allowed
// ------------------------------------------------------------------------
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
balances[from] = balances[from].sub(tokens);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
balances[to] = balances[to].add(tokens);
emit Transfer(from, to, tokens);
return true;
}
// ------------------------------------------------------------------------
// Returns the amount of tokens approved by the owner that can be
// transferred to the spender's account
// ------------------------------------------------------------------------
function allowance(address tokenOwner, address spender) public view returns (uint remaining) {
return allowed[tokenOwner][spender];
}
// ------------------------------------------------------------------------
// Token owner can approve for `spender` to transferFrom(...) `tokens`
// from the token owner's account. The `spender` contract function
// `receiveApproval(...)` is then executed
// ------------------------------------------------------------------------
function approveAndCall(address spender, uint tokens, bytes memory data) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, address(this), data);
return true;
}
// ------------------------------------------------------------------------
// Don't accept ETH
// ------------------------------------------------------------------------
function () external payable {
revert();
}
// ------------------------------------------------------------------------
// Owner can transfer out any accidentally sent ERC20 tokens
// ------------------------------------------------------------------------
function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
return ERC20Interface(tokenAddress).transfer(owner, tokens);
}
}

@ -34,6 +34,7 @@ defmodule Indexer.Block.Fetcher do
alias Indexer.Transform.{
AddressCoinBalances,
AddressCoinBalancesDaily,
Addresses,
AddressTokenBalances,
MintTransfers,
@ -55,6 +56,7 @@ defmodule Indexer.Block.Fetcher do
address_hash_to_fetched_balance_block_number: address_hash_to_fetched_balance_block_number,
addresses: Import.Runner.options(),
address_coin_balances: Import.Runner.options(),
address_coin_balances_daily: Import.Runner.options(),
address_token_balances: Import.Runner.options(),
blocks: Import.Runner.options(),
block_second_degree_relations: Import.Runner.options(),
@ -153,6 +155,14 @@ defmodule Indexer.Block.Fetcher do
transactions_params: transactions_with_receipts
}
|> AddressCoinBalances.params_set(),
coin_balances_params_daily_set =
%{
beneficiary_params: MapSet.to_list(beneficiary_params_set),
blocks_params: blocks,
logs_params: logs,
transactions_params: transactions_with_receipts
}
|> AddressCoinBalancesDaily.params_set(),
beneficiaries_with_gas_payment <-
beneficiary_params_set
|> add_gas_payments(transactions_with_receipts, blocks)
@ -164,6 +174,7 @@ defmodule Indexer.Block.Fetcher do
%{
addresses: %{params: addresses},
address_coin_balances: %{params: coin_balances_params_set},
address_coin_balances_daily: %{params: coin_balances_params_daily_set},
address_token_balances: %{params: address_token_balances},
blocks: %{params: blocks},
block_second_degree_relations: %{params: block_second_degree_relations_params},

@ -26,7 +26,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
]
alias Ecto.Changeset
alias EthereumJSONRPC.{FetchedBalances, Subscription}
alias EthereumJSONRPC.{Blocks, FetchedBalances, Subscription}
alias Explorer.Chain
alias Explorer.Chain.Cache.Accounts
alias Explorer.Counters.AverageBlockTime
@ -172,17 +172,25 @@ defmodule Indexer.Block.Realtime.Fetcher do
block_fetcher,
%{
address_coin_balances: %{params: address_coin_balances_params},
address_coin_balances_daily: %{params: address_coin_balances_daily_params},
address_hash_to_fetched_balance_block_number: address_hash_to_block_number,
addresses: %{params: addresses_params},
block_rewards: block_rewards
} = options
) do
with {:balances, {:ok, %{addresses_params: balances_addresses_params, balances_params: balances_params}}} <-
with {:balances,
{:ok,
%{
addresses_params: balances_addresses_params,
balances_params: balances_params,
balances_daily_params: balances_daily_params
}}} <-
{:balances,
balances(block_fetcher, %{
address_hash_to_block_number: address_hash_to_block_number,
addresses_params: addresses_params,
balances_params: address_coin_balances_params
balances_params: address_coin_balances_params,
balances_daily_params: address_coin_balances_daily_params
})},
{block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors),
chain_import_options =
@ -191,7 +199,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
|> put_in([:addresses, :params], balances_addresses_params)
|> put_in([:blocks, :params, Access.all(), :consensus], true)
|> put_in([:block_rewards], chain_import_block_rewards)
|> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params),
|> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params)
|> put_in([Access.key(:address_coin_balances_daily, %{}), :params], balances_daily_params),
{:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do
async_import_remaining_block_data(
imported,
@ -381,7 +390,33 @@ defmodule Indexer.Block.Realtime.Fetcher do
importable_balances_params = Enum.map(params_list, &Map.put(&1, :value_fetched_at, value_fetched_at))
{:ok, %{addresses_params: merged_addresses_params, balances_params: importable_balances_params}}
block_numbers =
params_list
|> Enum.map(&Map.get(&1, :block_number))
|> Enum.sort()
|> Enum.dedup()
block_timestamp_map =
Enum.reduce(block_numbers, %{}, fn block_number, map ->
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
Map.put(map, "#{block_number}", day)
end)
importable_balances_daily_params =
Enum.map(params_list, fn param ->
day = Map.get(block_timestamp_map, "#{param.block_number}")
Map.put(param, :day, day)
end)
{:ok,
%{
addresses_params: merged_addresses_params,
balances_params: importable_balances_params,
balances_daily_params: importable_balances_daily_params
}}
{:error, _} = error ->
error

@ -21,7 +21,7 @@ defmodule Indexer.Fetcher.BlockReward do
alias Indexer.{BufferedTask, Tracer}
alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor
alias Indexer.Fetcher.CoinBalance
alias Indexer.Transform.{AddressCoinBalances, Addresses}
alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses}
@behaviour BufferedTask
@ -245,9 +245,13 @@ defmodule Indexer.Fetcher.BlockReward do
addresses_params = Addresses.extract_addresses(%{block_reward_contract_beneficiaries: block_rewards_params})
address_coin_balances_params_set = AddressCoinBalances.params_set(%{beneficiary_params: block_rewards_params})
address_coin_balances_daily_params_set =
AddressCoinBalancesDaily.params_set(%{beneficiary_params: block_rewards_params})
Chain.import(%{
addresses: %{params: addresses_params},
address_coin_balances: %{params: address_coin_balances_params_set},
address_coin_balances_daily: %{params: address_coin_balances_daily_params_set},
block_rewards: %{params: block_rewards_params}
})
end

@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance do
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
alias EthereumJSONRPC.FetchedBalances
alias EthereumJSONRPC.{Blocks, FetchedBalances}
alias Explorer.Chain
alias Explorer.Chain.{Block, Hash}
alias Explorer.Chain.Cache.Accounts
@ -77,17 +77,22 @@ defmodule Indexer.Fetcher.CoinBalance do
# `{address, block}`, so take unique params only
unique_entries = Enum.uniq(entries)
unique_entry_count = Enum.count(unique_entries)
unique_filtered_entries =
Enum.filter(unique_entries, fn {_hash, block_number} ->
block_number >= first_block_to_index()
end)
unique_entry_count = Enum.count(unique_filtered_entries)
Logger.metadata(count: unique_entry_count)
Logger.debug(fn -> "fetching" end)
unique_entries
unique_filtered_entries
|> Enum.map(&entry_to_params/1)
|> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments)
|> case do
{:ok, fetched_balances} ->
run_fetched_balances(fetched_balances, unique_entries)
run_fetched_balances(fetched_balances, unique_filtered_entries)
{:error, reason} ->
Logger.error(
@ -97,7 +102,16 @@ defmodule Indexer.Fetcher.CoinBalance do
error_count: unique_entry_count
)
{:retry, unique_entries}
{:retry, unique_filtered_entries}
end
end
defp first_block_to_index do
string_value = Application.get_env(:indexer, :first_block)
case Integer.parse(string_value) do
{integer, ""} -> integer
_ -> 0
end
end
@ -127,11 +141,88 @@ defmodule Indexer.Fetcher.CoinBalance do
importable_balances_params = Enum.map(params_list, &Map.put(&1, :value_fetched_at, value_fetched_at))
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
block_numbers =
params_list
|> Enum.map(&Map.get(&1, :block_number))
|> Enum.sort()
|> Enum.dedup()
block_timestamp_map =
Enum.reduce(block_numbers, %{}, fn block_number, map ->
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
Map.put(map, "#{block_number}", day)
end)
importable_balances_daily_params =
params_list
|> Enum.map(fn balance_param ->
day = Map.get(block_timestamp_map, "#{balance_param.block_number}")
incoming_balance_daily_param = %{
address_hash: balance_param.address_hash,
day: day,
value: balance_param.value
}
incoming_balance_daily_param
end)
addresses_params = balances_params_to_address_params(importable_balances_params)
Chain.import(%{
addresses: %{params: addresses_params, with: :balance_changeset},
address_coin_balances: %{params: importable_balances_params},
address_coin_balances_daily: %{params: importable_balances_daily_params},
broadcast: broadcast_type
})
end
def import_fetched_daily_balances(%FetchedBalances{params_list: params_list}, broadcast_type \\ false) do
value_fetched_at = DateTime.utc_now()
importable_balances_params = Enum.map(params_list, &Map.put(&1, :value_fetched_at, value_fetched_at))
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
block_numbers =
params_list
|> Enum.map(&Map.get(&1, :block_number))
|> Enum.sort()
|> Enum.dedup()
block_timestamp_map =
Enum.reduce(block_numbers, %{}, fn block_number, map ->
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
Map.put(map, "#{block_number}", day)
end)
importable_balances_daily_params =
params_list
|> Enum.map(fn balance_param ->
day = Map.get(block_timestamp_map, "#{balance_param.block_number}")
incoming_balance_daily_param = %{
address_hash: balance_param.address_hash,
day: day,
value: balance_param.value
}
incoming_balance_daily_param
end)
addresses_params = balances_params_to_address_params(importable_balances_params)
Chain.import(%{
addresses: %{params: addresses_params, with: :balance_changeset},
address_coin_balances_daily: %{params: importable_balances_daily_params},
broadcast: broadcast_type
})
end

@ -15,10 +15,10 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
import Ecto.Query, only: [from: 2]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias EthereumJSONRPC.FetchedBalances
alias EthereumJSONRPC.{FetchedBalances}
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Address
alias Explorer.Chain.Address.CoinBalance
alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily}
alias Explorer.Chain.Cache.{Accounts, BlockNumber}
alias Explorer.Counters.AverageBlockTime
alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher
@ -86,6 +86,12 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
{:noreply, state}
end
def handle_cast({:fetch_and_import_daily_balances, block_number, address}, state) do
fetch_and_import_daily_balances(block_number, address, state.json_rpc_named_arguments)
{:noreply, state}
end
## Implementation
defp do_trigger_fetch(%Address{fetched_coin_balance_block_number: nil} = address, latest_block_number, _) do
@ -95,6 +101,14 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
defp do_trigger_fetch(address, latest_block_number, stale_balance_window) do
latest_by_day =
from(
cbd in CoinBalanceDaily,
where: cbd.address_hash == ^address.hash,
order_by: [desc: :day],
limit: 1
)
latest =
from(
cb in CoinBalance,
@ -105,15 +119,28 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
limit: 1
)
do_trigger_balance_fetch_query(address, latest_block_number, stale_balance_window, latest, latest_by_day)
end
defp do_trigger_balance_fetch_query(
address,
latest_block_number,
stale_balance_window,
query_balances,
query_balances_daily
) do
if address.fetched_coin_balance_block_number < stale_balance_window do
do_trigger_balance_daily_fetch_query(address, latest_block_number, query_balances_daily)
GenServer.cast(__MODULE__, {:fetch_and_update, latest_block_number, address})
{:stale, latest_block_number}
else
case Repo.one(latest) do
case Repo.one(query_balances) do
nil ->
# There is no recent coin balance to fetch, so we check to see how old the
# balance is on the address. If it is too old, we check again, just to be safe.
do_trigger_balance_daily_fetch_query(address, latest_block_number, query_balances_daily)
:current
%CoinBalance{value_fetched_at: nil, block_number: block_number} ->
@ -122,11 +149,19 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
{:pending, block_number}
%CoinBalance{} ->
do_trigger_balance_daily_fetch_query(address, latest_block_number, query_balances_daily)
:current
end
end
end
defp do_trigger_balance_daily_fetch_query(address, latest_block_number, query) do
if Repo.one(query) == nil do
GenServer.cast(__MODULE__, {:fetch_and_import_daily_balances, latest_block_number, address})
end
end
defp fetch_and_import(block_number, address, json_rpc_named_arguments) do
case fetch_balances(block_number, address, json_rpc_named_arguments) do
{:ok, fetched_balances} -> do_import(fetched_balances)
@ -134,6 +169,13 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
end
defp fetch_and_import_daily_balances(block_number, address, json_rpc_named_arguments) do
case fetch_balances(block_number, address, json_rpc_named_arguments) do
{:ok, fetched_balances} -> do_import_daily_balances(fetched_balances)
_ -> :ok
end
end
defp fetch_and_update(block_number, address, json_rpc_named_arguments) do
case fetch_balances(block_number, address, json_rpc_named_arguments) do
{:ok, %{params_list: []}} ->
@ -165,6 +207,13 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
end
defp do_import_daily_balances(%FetchedBalances{} = fetched_balances) do
case CoinBalanceFetcher.import_fetched_daily_balances(fetched_balances, :on_demand) do
{:ok, %{addresses: [address]}} -> {:ok, address}
_ -> :error
end
end
defp latest_block_number do
BlockNumber.get_max()
end

@ -0,0 +1,155 @@
defmodule Indexer.Transform.AddressCoinBalancesDaily do
@moduledoc """
Extracts `Explorer.Chain.Address.CoinBalanceDaily` params from other schema's params.
"""
alias EthereumJSONRPC.Blocks
def params_set(%{} = import_options) do
Enum.reduce(import_options, MapSet.new(), &reducer/2)
end
defp reducer({:beneficiary_params, beneficiary_params}, acc) when is_list(beneficiary_params) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
block_numbers =
beneficiary_params
|> Enum.map(&Map.get(&1, :block_number))
|> Enum.sort()
|> Enum.dedup()
block_timestamp_map =
Enum.reduce(block_numbers, %{}, fn block_number, map ->
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
Map.put(map, "#{block_number}", day)
end)
Enum.into(beneficiary_params, acc, fn %{
address_hash: address_hash,
block_number: block_number
}
when is_binary(address_hash) and is_integer(block_number) ->
day = Map.get(block_timestamp_map, "#{block_number}")
%{address_hash: address_hash, day: day}
end)
end
defp reducer({:blocks_params, blocks_params}, acc) when is_list(blocks_params) do
# a block MUST have a miner_hash and number
Enum.into(blocks_params, acc, fn %{miner_hash: address_hash, number: block_number, timestamp: block_timestamp}
when is_binary(address_hash) and is_integer(block_number) ->
day = DateTime.to_date(block_timestamp)
%{address_hash: address_hash, day: day}
end)
end
defp reducer({:internal_transactions_params, internal_transactions_params}, initial)
when is_list(internal_transactions_params) do
Enum.reduce(internal_transactions_params, initial, &internal_transactions_params_reducer/2)
end
defp reducer({:logs_params, logs_params}, acc) when is_list(logs_params) do
# a log MUST have address_hash and block_number
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
block_numbers =
logs_params
|> Enum.map(&Map.get(&1, :block_number))
|> Enum.sort()
|> Enum.dedup()
block_timestamp_map =
Enum.reduce(block_numbers, %{}, fn block_number, map ->
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
Map.put(map, "#{block_number}", day)
end)
logs_params
|> Enum.into(acc, fn
%{address_hash: address_hash, block_number: block_number}
when is_binary(address_hash) and is_integer(block_number) ->
day = Map.get(block_timestamp_map, "#{block_number}")
%{address_hash: address_hash, day: day}
%{type: "pending"} ->
nil
end)
|> Enum.reject(fn val -> is_nil(val) end)
|> MapSet.new()
end
defp reducer({:transactions_params, transactions_params}, initial) when is_list(transactions_params) do
Enum.reduce(transactions_params, initial, &transactions_params_reducer/2)
end
defp reducer({:block_second_degree_relations_params, block_second_degree_relations_params}, initial)
when is_list(block_second_degree_relations_params),
do: initial
defp internal_transactions_params_reducer(
%{block_number: block_number} = internal_transaction_params,
acc
)
when is_integer(block_number) do
case internal_transaction_params do
%{type: "call"} ->
acc
%{type: "create", error: _} ->
acc
%{type: "create", created_contract_address_hash: address_hash} when is_binary(address_hash) ->
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{:ok, %Blocks{blocks_params: [%{timestamp: block_timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(block_timestamp)
MapSet.put(acc, %{address_hash: address_hash, day: day})
%{type: "selfdestruct", from_address_hash: from_address_hash, to_address_hash: to_address_hash}
when is_binary(from_address_hash) and is_binary(to_address_hash) ->
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{:ok, %Blocks{blocks_params: [%{timestamp: block_timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(block_timestamp)
acc
|> MapSet.put(%{address_hash: from_address_hash, day: day})
|> MapSet.put(%{address_hash: to_address_hash, day: day})
end
end
defp transactions_params_reducer(
%{block_number: block_number, from_address_hash: from_address_hash} = transaction_params,
initial
)
when is_binary(from_address_hash) do
# a transaction MUST have a `from_address_hash`
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{:ok, %Blocks{blocks_params: [%{timestamp: block_timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(block_number..block_number, json_rpc_named_arguments)
day = DateTime.to_date(block_timestamp)
acc = MapSet.put(initial, %{address_hash: from_address_hash, day: day})
# `to_address_hash` is optional
case transaction_params do
%{to_address_hash: to_address_hash} when is_binary(to_address_hash) ->
MapSet.put(acc, %{address_hash: to_address_hash, day: day})
_ ->
acc
end
end
end

@ -79,6 +79,8 @@ defmodule Indexer.Block.FetcherTest do
block_quantity = integer_to_quantity(block_number)
miner_hash = "0x0000000000000000000000000000000000000000"
res = eth_block_number_fake_response(block_quantity)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
@ -137,6 +139,17 @@ defmodule Indexer.Block.FetcherTest do
{:ok, [%{id: id, result: []}]}
end
end)
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
EthereumJSONRPC.Geth ->
EthereumJSONRPC.Mox
@ -381,10 +394,78 @@ defmodule Indexer.Block.FetcherTest do
end)
# async requests need to be grouped in one expect because the order is non-deterministic while multiple expect
# calls on the same name/arity are used in order
|> expect(:json_rpc, 5, fn json, _options ->
|> expect(:json_rpc, 11, fn json, _options ->
[request] = json
case request do
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
} ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd5830108048650617269747986312e32322e31826c69",
"gasLimit" => "0x69fe20",
"gasUsed" => "0xc512",
"hash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"number" => "0x25",
"parentHash" => "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e",
"receiptsRoot" => "0xd300311aab7dcc98c05ac3f1893629b2c9082c189a0a0c76f4f63e292ac419d5",
"sealFields" => [
"0x84120a71de",
"0xb841fcdb570511ec61edda93849bb7c6b3232af60feb2ea74e4035f0143ab66dfdd00f67eb3eda1adddbb6b572db1e0abd39ce00f9b3ccacb9f47973279ff306fe5401"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"fcdb570511ec61edda93849bb7c6b3232af60feb2ea74e4035f0143ab66dfdd00f67eb3eda1adddbb6b572db1e0abd39ce00f9b3ccacb9f47973279ff306fe5401",
"size" => "0x2cf",
"stateRoot" => "0x2cd84079b0d0c267ed387e3895fd1c1dc21ff82717beb1132adac64276886e19",
"step" => "302674398",
"timestamp" => "0x5a343956",
"totalDifficulty" => "0x24ffffffffffffffffffffffffedf78dfd",
"transactions" => [
%{
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"blockNumber" => "0x25",
"chainId" => "0x4d",
"condition" => nil,
"creates" => nil,
"from" => from_address_hash,
"gas" => "0x47b760",
"gasPrice" => "0x174876e800",
"hash" => transaction_hash,
"input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"nonce" => "0x4",
"publicKey" =>
"0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9",
"r" => "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01",
"raw" =>
"0xf88a0485174876e8008347b760948bf38d4764929064f2d4d3a56520a76ab3df415b80a410855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef81bea0a7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01a01f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f",
"s" => "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f",
"standardV" => "0x1",
"to" => to_address_hash,
"transactionIndex" => "0x0",
"v" => "0xbe",
"value" => "0x0"
}
],
"transactionsRoot" => "0x68e314a05495f390f9cd0c36267159522e5450d2adf254a74567b452e767bf34",
"uncles" => []
}
}
]}
%{id: id, method: "eth_getBalance", params: [^to_address_hash, ^block_quantity]} ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x1"}]}
@ -662,6 +743,22 @@ defmodule Indexer.Block.FetcherTest do
}
%{id: id, method: "trace_block"} ->
block_quantity = integer_to_quantity(block_number)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
%{
id: id,
result: [
@ -742,4 +839,40 @@ defmodule Indexer.Block.FetcherTest do
counts.buffer == 0 and counts.tasks == 0
end)
end
defp eth_block_number_fake_response(block_quantity) do
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x0000000000000000000000000000000000000000",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end
end

@ -205,7 +205,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do
}
]}
end)
|> expect(:json_rpc, 2, fn
|> expect(:json_rpc, 5, fn
[
%{id: 0, jsonrpc: "2.0", method: "trace_block", params: ["0x3C365F"]},
%{id: 1, jsonrpc: "2.0", method: "trace_block", params: ["0x3C3660"]}
@ -217,6 +217,124 @@ defmodule Indexer.Block.Realtime.FetcherTest do
%{id: 1, jsonrpc: "2.0", result: []}
]}
[
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: ["0x3C365F", true]
}
],
_ ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd583010b088650617269747986312e32372e32826c69",
"gasLimit" => "0x7a1200",
"gasUsed" => "0x2886e",
"hash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2",
"number" => "0x3c365f",
"parentHash" => "0x57f6d66e07488defccd5216c4d2968dd6afd3bd32415e284de3b02af6535e8dc",
"receiptsRoot" => "0x111be72e682cea9c93e02f1ef503fb64aa821b2ef510fd9177c49b37d0af98b5",
"sealFields" => [
"0x841246c63f",
"0xb841ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700",
"size" => "0x33e",
"stateRoot" => "0x7f73f5fb9f891213b671356126c31e9795d038844392c7aa8800ed4f52307209",
"step" => "306628159",
"timestamp" => "0x5b61df3b",
"totalDifficulty" => "0x3c365effffffffffffffffffffffffed7f0362",
"transactions" => [
%{
"blockHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc",
"blockNumber" => "0x3c365f",
"chainId" => "0x63",
"condition" => nil,
"creates" => nil,
"from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16",
"gas" => "0x3d9c5",
"gasPrice" => "0x3b9aca00",
"hash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8",
"input" =>
"0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005",
"nonce" => "0x65b",
"publicKey" =>
"0x89c2123ed4b5d141cf1f4b6f5f3d754418f03aea2e870a1c50888d94bf5531f74237e2fea72d0bc198ef213272b62c6869615720757255e6cba087f9db6e759f",
"r" => "0x55a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995",
"raw" =>
"0xf8f582065b843b9aca008303d9c594698bf6943bab687b2756394624aa183f434f65da8901158e4f216242a000b8848841ac11000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000581eaa0055a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995a06affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7",
"s" => "0x6affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7",
"standardV" => "0x1",
"to" => "0x698bf6943bab687b2756394624aa183f434f65da",
"transactionIndex" => "0x0",
"v" => "0xea",
"value" => "0x1158e4f216242a000"
}
],
"transactionsRoot" => "0xd7c39a93eafe0bdcbd1324c13dcd674bed8c9fa8adbf8f95bf6a59788985da6f",
"uncles" => ["0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cd"]
}
}
]}
[
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: ["0x3C3660", true]
}
],
_ ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd583010a068650617269747986312e32362e32826c69",
"gasLimit" => "0x7a1200",
"gasUsed" => "0x0",
"hash" => "0xfb483e511d316fa4072694da3f7abc94b06286406af45061e5e681395bdc6815",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3",
"number" => "0x3c3660",
"parentHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x841246c640",
"0xb84114db3fd7526b7ea3635f5c85c30dd8a645453aa2f8afe5fd33fe0ec663c9c7b653b0fb5d8dc7d0b809674fa9dca9887d1636a586bf62191da22255eb068bf20800"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"14db3fd7526b7ea3635f5c85c30dd8a645453aa2f8afe5fd33fe0ec663c9c7b653b0fb5d8dc7d0b809674fa9dca9887d1636a586bf62191da22255eb068bf20800",
"size" => "0x243",
"stateRoot" => "0x3174c461989e9f99e08fa9b4ffb8bce8d9a281c8fc9f80694bb9d3acd4f15559",
"step" => "306628160",
"timestamp" => "0x5b61df40",
"totalDifficulty" => "0x3c365fffffffffffffffffffffffffed7f0360",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
]}
[
%{
id: 0,

@ -117,6 +117,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert count(Chain.Block.Reward) == 0
parent = self()
@ -190,6 +205,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
parent = self()
pid =
@ -320,6 +350,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert count(Chain.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0
@ -408,6 +453,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert count(Chain.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0
@ -486,6 +546,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert count(Chain.Block.Reward) == 1
assert count(Chain.Address.CoinBalance) == 1
@ -625,6 +700,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert count(Chain.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0
@ -684,4 +774,40 @@ defmodule Indexer.Fetcher.BlockRewardTest do
do_wait_until(parent, ref, producer)
end
end
defp eth_block_number_fake_response(block_quantity) do
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x0000000000000000000000000000000000000000",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end
end

@ -117,6 +117,21 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end)
res = eth_block_number_fake_response("0x65")
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: ["0x65", true]
}
],
_ ->
{:ok, [res]}
end)
assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101}
{:ok, expected_wei} = Wei.cast(2)
@ -144,6 +159,20 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end)
EthereumJSONRPC.Mox
|> expect(:json_rpc, 1, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: ["0x66", true]
}
],
_ ->
res = eth_block_number_fake_response("0x66")
{:ok, [res]}
end)
assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 102}
{:ok, expected_wei} = Wei.cast(2)
@ -154,4 +183,40 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
)
end
end
defp eth_block_number_fake_response(block_quantity) do
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x0000000000000000000000000000000000000000",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end
end

@ -58,6 +58,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
@ -114,6 +129,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
@ -178,6 +208,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
end
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -254,6 +299,25 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
{:ok, %Hash{bytes: address_hash_bytes}} = Hash.Address.cast(hash_data)
entries = Enum.map(block_quantities, &{address_hash_bytes, quantity_to_integer(&1)})
res1 = eth_block_number_fake_response("0x1")
res2 = eth_block_number_fake_response("0x2")
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
],
_ ->
{:ok, [res1]}
end)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x2", true]}
],
_ ->
{:ok, [res2]}
end)
case CoinBalance.run(entries, json_rpc_named_arguments) do
:ok ->
balances = Repo.all(from(balance in Address.CoinBalance, where: balance.address_hash == ^hash_data))
@ -314,16 +378,33 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
test "retries none if all imported and no fetch errors", %{json_rpc_named_arguments: json_rpc_named_arguments} do
%Hash{bytes: address_hash_bytes} = address_hash()
entries = [{address_hash_bytes, block_number()}]
block_number = block_number()
entries = [{address_hash_bytes, block_number}]
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, method: "eth_getBalance", params: [_, _]}], _ ->
{:ok, [%{id: id, result: "0x1"}]}
end)
block_quantity = integer_to_quantity(block_number)
res = eth_block_number_fake_response(block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
assert :ok = CoinBalance.run(entries, json_rpc_named_arguments)
end
test "retries retries fetch errors if all imported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
test "retries fetch errors if all imported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
%Hash{bytes: address_hash_bytes} = address_hash()
bad_block_number = block_number()
good_block_number = block_number()
@ -359,6 +440,22 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
{:ok, responses}
end)
good_block_quantity = integer_to_quantity(good_block_number)
res_good = eth_block_number_fake_response(good_block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^good_block_quantity, true]
}
],
[] ->
{:ok, [res_good]}
end)
assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} =
CoinBalance.run(
[{address_hash_bytes, good_block_number}, {address_hash_bytes, bad_block_number}],
@ -374,4 +471,40 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
Process.sleep(100)
wait(producer)
end
defp eth_block_number_fake_response(block_quantity) do
%{
id: 0,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0x0000000000000000000000000000000000000000",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end
end

@ -32,7 +32,7 @@
"ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e6c614dfe3bcff2d575ce16d815dbd43f4ee1844599a83de1eea81976a31c174"},
"ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82d89d4e6a9f7f7f04783b07e8b0af968e0be2f01ee4b39047fe727c5c07471"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"ex_abi": {:git, "https://github.com/poanetwork/ex_abi.git", "ef450816ef4ca98648125577b675ab584251a883", [branch: "master"]},
"ex_abi": {:hex, :ex_abi, "0.4.0", "ff7e7f5b56c228b117e1f54e80c668a8f0424c275f233a50373548b70d99bd5c", [:mix], [{:exth_crypto, "~> 0.1.6", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm", "2d33499de38c54531103e58530d0453863fb6149106327f691001873b0556e68"},
"ex_cldr": {:hex, :ex_cldr, "2.7.2", "d79a1af6ed12630a15175d2b88d4381b22db5d6f835c5da8de132f0cf712b233", [:mix], [{:cldr_utils, "~> 2.1", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "52344684e4e0ff046e1929ae019c0b3c6b122c77c948a43be30684015f2036e2"},
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.3.0", "bffae489416b8b05d4683403263f5d62aae17de70c24ff915a533541fea514de", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7d8ba3738b24d9c7d31bc63b131785b444ed05388fd1cc0958216eb5992d79d6"},
"ex_cldr_lists": {:hex, :ex_cldr_lists, "2.2.0", "b99f8752d098fc6ba5f083bbd0b25d0d01e36b0042155cf6abd5f205306ba849", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "89e25d539a4126472560da2e28b6f1aeb859f5afc0778d6b594029c4226d1775"},
@ -55,7 +55,7 @@
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm", "dd3a7ea5e3e87ee9df29452dd9560709b4c7cc8141537d0b070155038d92bdf1"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm", "3e3d7156a272950373ce5a4018b1490bea26676f8d6a7d409f6fac8568b8cb9a"},
"httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "07967c56199f716ce9adb27415ccea1bd76c44f777dd0a6d4166c3d932f37fdf"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"},

Loading…
Cancel
Save