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. 15
      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. 206
      apps/explorer/lib/explorer/chain.ex
  27. 52
      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. 162
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  41. 230
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  42. 8
      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/block_scout_web/router.ex:1
lib/phoenix/router.ex:324 lib/phoenix/router.ex:324
lib/block_scout_web/views/layout_view.ex:143 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 ## Current
### Features ### 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 ### Fixes
### Chore ### 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 ## 3.1.2-beta

@ -84,6 +84,10 @@ config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController,
# days # days
history_size: 30 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, config :ex_cldr,
default_locale: "en", default_locale: "en",
default_backend: BlockScoutWeb.Cldr default_backend: BlockScoutWeb.Cldr

@ -44,8 +44,15 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
def coinsupply(conn, _params) do def coinsupply(conn, _params) do
cached_coin_total_supply_wei = AddressSumMinusBurnt.get_sum_minus_burnt() 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 = cached_coin_total_supply =
%Wei{value: Decimal.new(cached_coin_total_supply_wei)} %Wei{value: Decimal.new(coin_total_supply_wei)}
|> Wei.to(:ether) |> Wei.to(:ether)
render(conn, "coinsupply.json", cached_coin_total_supply) render(conn, "coinsupply.json", cached_coin_total_supply)

@ -5,16 +5,30 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Transaction
def gettxinfo(conn, params) do def gettxinfo(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), {: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 paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction_hash, paging_options) logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs) {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, %{ render(conn, :gettxinfo, %{
transaction: transaction, transaction: transaction_updated,
block_height: Chain.block_height(), block_height: Chain.block_height(),
logs: logs, logs: logs,
next_page_params: next_page_params(next_page, logs, params) next_page_params: next_page_params(next_page, logs, params)

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

@ -53,6 +53,14 @@
<!-- Verify in other explorers --> <!-- Verify in other explorers -->
<!-- <%= render BlockScoutWeb.AddressView, "_verify_other_explorers.html", hash: hash(@transaction), type: "tx" %> --> <!-- <%= render BlockScoutWeb.AddressView, "_verify_other_explorers.html", hash: hash(@transaction), type: "tx" %> -->
<hr> <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 --> <!-- Block Hash -->
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Block Number" %> </dt> <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(_, 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 def balance_percentage(%Address{fetched_coin_balance: balance}, total_supply) do
if total_supply > 0 do if total_supply > 0 do
balance balance

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

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

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

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

@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#, elixir-format #, 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" msgid " Token Transfer"
msgstr "" msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Block Confirmations"
msgstr "" msgstr ""
@ -213,12 +213,12 @@ msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Block Number"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:32 #: lib/block_scout_web/views/transaction_view.ex:33
msgid "Block Pending" msgid "Block Pending"
msgstr "" msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:325 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:322 #: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -558,12 +558,12 @@ msgid "During times when the network is busy (i.e during ICOs) it can take a whi
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:136 #: lib/block_scout_web/views/transaction_view.ex:137
msgid "ERC-20 " msgid "ERC-20 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137 #: lib/block_scout_web/views/transaction_view.ex:138
msgid "ERC-721 " msgid "ERC-721 "
msgstr "" msgstr ""
@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:240 #: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32 #: 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/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29 #: 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:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -666,7 +666,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73 #: 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" msgid "Nonce"
msgstr "" msgstr ""
@ -753,8 +753,8 @@ msgid "Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115 #: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:119 #: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
@ -776,7 +776,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: 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" msgid "Success"
msgstr "" msgstr ""
@ -789,7 +789,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: 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/_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" msgid "TX Fee"
msgstr "" msgstr ""
@ -891,7 +891,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12 #: 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/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" msgid "Token Transfer"
msgstr "" msgstr ""
@ -903,10 +903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 #: 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/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: 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/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90 #: 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/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" msgid "Token Transfers"
msgstr "" msgstr ""
@ -922,7 +922,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18 #: 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" msgid "Transaction"
msgstr "" msgstr ""
@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283 #: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
@ -1041,12 +1041,12 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52 #: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30 #: 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" msgid "Market Cap"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:221 #: lib/block_scout_web/views/transaction_view.ex:232
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
@ -1159,8 +1159,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 #: 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:250
#: lib/block_scout_web/views/transaction_view.ex:269 #: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -1201,14 +1201,14 @@ msgid "RPC"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Raw Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: 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/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" msgid "Raw Trace"
msgstr "" msgstr ""
@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transaction Speed"
msgstr "" msgstr ""
@ -1518,7 +1518,7 @@ msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "UTF-8"
msgstr "" msgstr ""
@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277 #: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -1779,7 +1779,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: 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/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" msgid "Metadata"
msgstr "" msgstr ""
@ -1799,15 +1799,15 @@ msgid "Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134 #: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:147 #: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input" msgid "Copy Txn Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: 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/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" msgid "Blocks Validated"
msgstr "" 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/_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:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: 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" msgid "Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: 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" msgid "Coin Balance History"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_view.ex:316 #: lib/block_scout_web/views/address_view.ex:327
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
@ -1837,8 +1837,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: 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/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: 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/address_view.ex:324
#: lib/block_scout_web/views/transaction_view.ex:380 #: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -1847,15 +1847,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: 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/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: 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/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:381 #: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62 #: 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/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 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -1864,7 +1864,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: 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/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 #: 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" msgid "Tokens"
msgstr "" msgstr ""
@ -1876,31 +1876,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: 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/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50 #: 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" msgid "Transactions"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid " Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8 #: 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/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" msgid "Token Burning"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid " Token Minting"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10 #: 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/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" msgid "Token Minting"
msgstr "" msgstr ""
@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62 #: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward" msgid "Chore Reward"
msgstr "" 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] "" msgstr[1] ""
#, elixir-format #, 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" msgid " Token Transfer"
msgstr "" msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Block Confirmations"
msgstr "" msgstr ""
@ -213,12 +213,12 @@ msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Block Number"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:32 #: lib/block_scout_web/views/transaction_view.ex:33
msgid "Block Pending" msgid "Block Pending"
msgstr "" msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:325 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:322 #: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -558,12 +558,12 @@ msgid "During times when the network is busy (i.e during ICOs) it can take a whi
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:136 #: lib/block_scout_web/views/transaction_view.ex:137
msgid "ERC-20 " msgid "ERC-20 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137 #: lib/block_scout_web/views/transaction_view.ex:138
msgid "ERC-721 " msgid "ERC-721 "
msgstr "" msgstr ""
@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:240 #: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32 #: 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/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29 #: 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:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -666,7 +666,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73 #: 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" msgid "Nonce"
msgstr "" msgstr ""
@ -753,8 +753,8 @@ msgid "Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115 #: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:119 #: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
@ -776,7 +776,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: 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" msgid "Success"
msgstr "" msgstr ""
@ -789,7 +789,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: 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/_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" msgid "TX Fee"
msgstr "" msgstr ""
@ -891,7 +891,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12 #: 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/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" msgid "Token Transfer"
msgstr "" msgstr ""
@ -903,10 +903,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 #: 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/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: 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/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90 #: 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/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" msgid "Token Transfers"
msgstr "" msgstr ""
@ -922,7 +922,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18 #: 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" msgid "Transaction"
msgstr "" msgstr ""
@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283 #: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
@ -1041,12 +1041,12 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52 #: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30 #: 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" msgid "Market Cap"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:221 #: lib/block_scout_web/views/transaction_view.ex:232
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
@ -1159,8 +1159,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 #: 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:250
#: lib/block_scout_web/views/transaction_view.ex:269 #: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -1201,14 +1201,14 @@ msgid "RPC"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Raw Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: 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/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" msgid "Raw Trace"
msgstr "" msgstr ""
@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transaction Speed"
msgstr "" msgstr ""
@ -1518,7 +1518,7 @@ msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "UTF-8"
msgstr "" msgstr ""
@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277 #: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -1779,7 +1779,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: 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/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" msgid "Metadata"
msgstr "" msgstr ""
@ -1799,15 +1799,15 @@ msgid "Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134 #: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:147 #: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input" msgid "Copy Txn Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: 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/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" msgid "Blocks Validated"
msgstr "" 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/_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:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: 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" msgid "Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: 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" msgid "Coin Balance History"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_view.ex:316 #: lib/block_scout_web/views/address_view.ex:327
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
@ -1837,8 +1837,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: 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/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: 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/address_view.ex:324
#: lib/block_scout_web/views/transaction_view.ex:380 #: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -1847,15 +1847,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: 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/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: 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/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:381 #: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62 #: 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/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 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -1864,7 +1864,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: 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/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 #: 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" msgid "Tokens"
msgstr "" msgstr ""
@ -1876,31 +1876,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: 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/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50 #: 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" msgid "Transactions"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid " Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8 #: 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/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" msgid "Token Burning"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid " Token Minting"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10 #: 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/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" msgid "Token Minting"
msgstr "" msgstr ""
@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62 #: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward" msgid "Chore Reward"
msgstr "" 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) 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: 1000, block_number: block.number)
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.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"}) 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) 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 [ expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{ %{
id: id, id: id,
@ -122,6 +146,17 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end) end)
expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [
%{
id: 0,
method: "eth_getBlockByNumber",
params: ["0x65", true]
}
],
_ ->
{:ok, [res]}
end)
response = response =
conn conn
|> get("/api", params) |> get("/api", params)
@ -2719,4 +2754,40 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> put_in(["properties", "result"], result) |> put_in(["properties", "result"], result)
|> ExJsonSchema.Schema.resolve() |> ExJsonSchema.Schema.resolve()
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 end

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

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
import Mox
@moduletag capture_log: true @moduletag capture_log: true
describe "gettxreceiptstatus" do describe "gettxreceiptstatus" do
@ -520,7 +522,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"index" => "#{log.index}" "index" => "#{log.index}"
} }
], ],
"next_page_params" => nil "next_page_params" => nil,
"revertReason" => ""
} }
schema = schema =
@ -576,6 +579,184 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["status"] == "1" assert response["status"] == "1"
assert response["message"] == "OK" assert response["message"] == "OK"
end 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 end
defp resolve_schema(result \\ %{}) do defp resolve_schema(result \\ %{}) do

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

@ -3,6 +3,7 @@
"compiler_version": "v0.5.11+commit.c082d0b4", "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; }}", "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", "expected_bytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158203e59bfb9a5a2e55d38231922c86d8b2ec9b66cb2f6595613674bc4e15290b60764736f6c634300050b0032",
"tx_input": "6080604052600a60005534801561001557600080fd5b50610169806100256000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158207809bc828bbcd3de3e6b6483facb0c5902901fc8827283c749c8ea0702eb871f64736f6c634300050b0032",
"external_libraries": { "external_libraries": {
"BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73" "BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73"
}, },

@ -215,7 +215,18 @@ defmodule EthereumJSONRPC do
@spec fetch_beneficiaries([block_number], json_rpc_named_arguments) :: @spec fetch_beneficiaries([block_number], json_rpc_named_arguments) ::
{:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore {:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore
def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do 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 end
@doc """ @doc """
@ -294,9 +305,17 @@ defmodule EthereumJSONRPC do
@doc """ @doc """
Fetches internal transactions for entire blocks from variant API. 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( Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions(
params_list, filtered_block_numbers,
json_rpc_named_arguments json_rpc_named_arguments
) )
end end
@ -464,4 +483,13 @@ defmodule EthereumJSONRPC do
{:ok, Blocks.from_responses(responses, id_to_params)} {:ok, Blocks.from_responses(responses, id_to_params)}
end end
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 end

@ -235,6 +235,49 @@ defmodule EthereumJSONRPC.Block do
} }
end 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 """ @doc """
Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0` Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0`
@ -437,7 +480,7 @@ defmodule EthereumJSONRPC.Block do
end end
defp entry_to_elixir({key, quantity}) 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 not is_nil(quantity) do
{key, quantity_to_integer(quantity)} {key, quantity_to_integer(quantity)}
end end

@ -58,16 +58,6 @@ defmodule EthereumJSONRPC.Encoder do
end) end)
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 def decode_result(%{id: id, result: result}, function_selector) do
types_list = List.wrap(function_selector.returns) types_list = List.wrap(function_selector.returns)

@ -9,7 +9,7 @@ defmodule EthereumJSONRPC.Transaction do
""" """
require Logger require Logger
import EthereumJSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1, integer_to_quantity: 1, request: 1]
alias EthereumJSONRPC alias EthereumJSONRPC
@ -313,6 +313,20 @@ defmodule EthereumJSONRPC.Transaction do
nil nil
end 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 # 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 # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format # hash format

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

@ -28,11 +28,16 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Address.CoinBalance, Address.CoinBalance,
Address.CoinBalanceDaily,
Address.CurrentTokenBalance, Address.CurrentTokenBalance,
Address.TokenBalance, Address.TokenBalance,
Block, Block,
@ -1517,7 +1522,8 @@ defmodule Explorer.Chain do
from( from(
a0 in Address, a0 in Address,
select: fragment("SUM(a0.fetched_coin_balance)"), 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 Repo.one!(query) || 0
@ -1528,7 +1534,8 @@ defmodule Explorer.Chain do
query = query =
from( from(
a0 in Address, 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 Repo.one!(query) || 0
@ -2160,6 +2167,27 @@ defmodule Explorer.Chain do
end end
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 defp block_status({number, timestamp}) do
now = DateTime.utc_now() now = DateTime.utc_now()
last_block_period = DateTime.diff(now, timestamp, :millisecond) 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_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 """ @doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`. `unit`.
@ -2716,6 +2806,42 @@ defmodule Explorer.Chain do
|> Data.to_string() |> Data.to_string()
end 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 """ @doc """
Checks if an address is a contract 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 -> transaction.contracts_creation_internal_transaction && transaction.contracts_creation_internal_transaction.input ->
Data.to_string(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 -> transaction.contracts_creation_transaction && transaction.contracts_creation_transaction.input ->
Data.to_string(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 def address_to_coin_balances(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
balances_raw =
address_hash address_hash
|> CoinBalance.fetch_coin_balances(paging_options) |> CoinBalance.fetch_coin_balances(paging_options)
|> page_coin_balances(paging_options) |> page_coin_balances(paging_options)
|> Repo.all() |> Repo.all()
|> Enum.dedup_by(fn record ->
if record.delta == Decimal.new(0) do if Enum.empty?(balances_raw) do
:dup balances_raw
else else
System.unique_integer() balances_raw_filtered =
end 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) 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 end
def get_coin_balance(address_hash, block_number) do def get_coin_balance(address_hash, block_number) do
@ -3455,8 +3636,9 @@ defmodule Explorer.Chain do
|> Repo.one() |> Repo.one()
address_hash address_hash
|> CoinBalance.balances_by_day(latest_block_timestamp) |> CoinBalanceDaily.balances_by_day()
|> Repo.all() |> Repo.all()
|> Enum.sort_by(fn %{date: d} -> {d.year, d.month, d.day} end)
|> replace_last_value(latest_block_timestamp) |> replace_last_value(latest_block_timestamp)
|> normalize_balances_by_day() |> normalize_balances_by_day()
end end
@ -3471,7 +3653,6 @@ defmodule Explorer.Chain do
defp normalize_balances_by_day(balances_by_day) do defp normalize_balances_by_day(balances_by_day) do
result = result =
balances_by_day balances_by_day
|> Enum.map(fn day -> Map.take(day, [:date, :value]) end)
|> Enum.filter(fn day -> day.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, :date, &to_string(&1)) end)
|> Enum.map(fn day -> Map.update!(day, :value, &Wei.to(&1, :ether)) end) |> Enum.map(fn day -> Map.update!(day, :value, &Wei.to(&1, :ether)) end)
@ -4269,4 +4450,11 @@ defmodule Explorer.Chain do
block_index block_index
end end
end end
defp find_block_timestamp(number) do
Block
|> where([b], b.number == ^number)
|> select([b], b.timestamp)
|> Repo.one()
end
end end

@ -72,16 +72,18 @@ defmodule Explorer.Chain.Address.CoinBalance do
The last coin balance from an Address is the last block indexed. The last coin balance from an Address is the last block indexed.
""" """
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
query =
from( from(
cb in CoinBalance, cb in CoinBalance,
where: cb.address_hash == ^address_hash, where: cb.address_hash == ^address_hash,
where: not is_nil(cb.value), where: not is_nil(cb.value),
inner_join: b in Block,
on: cb.block_number == b.number,
order_by: [desc: :block_number], order_by: [desc: :block_number],
limit: ^page_size, select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")}
select_merge: %{delta: fragment("value - coalesce(lag(value, 1) over (order by block_number), 0)")}, )
select_merge: %{block_timestamp: b.timestamp}
from(balance in subquery(query),
where: balance.delta != 0,
limit: ^page_size
) )
end 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. 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 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 CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number) |> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash) |> 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)) |> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_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)}) |> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end end
def limit_time_interval(query, nil) do def limit_time_interval(query, days_to_consider, nil) do
query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'")) query
|> where(
[cb, b],
b.timestamp >=
fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end end
def limit_time_interval(query, %{timestamp: timestamp}) do def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC")) query
|> where(
[cb, b],
b.timestamp >=
fragment(
"(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
^timestamp,
^"Etc/UTC",
^%Postgrex.Interval{days: days_to_consider}
)
)
end end
def last_coin_balance_timestamp(address_hash) do def last_coin_balance_timestamp(address_hash) do
coin_balance_query =
CoinBalance CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash) |> where([cb], cb.address_hash == ^address_hash)
|> last(:block_number) |> last(:block_number)
|> select([cb, b], %{timestamp: b.timestamp, value: cb.value}) |> 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 end
def changeset(%__MODULE__{} = balance, params) do 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 try do
result = Chain.fetch_sum_coin_total_supply_minus_burnt() 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) set_sum_minus_burnt(result)
rescue rescue
e -> 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.Address.CoinBalances,
Runner.Blocks, Runner.Blocks,
Runner.StakingPools, Runner.StakingPools,
Runner.StakingPoolsDelegators Runner.StakingPoolsDelegators,
Runner.Address.CoinBalancesDaily
] ]
@impl Stage @impl Stage

@ -326,6 +326,7 @@ defmodule Explorer.Chain.SmartContract do
defp upsert_contract_methods(changeset), do: changeset defp upsert_contract_methods(changeset), do: changeset
defp error_message(:compilation), do: "There was an error compiling your contract." 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(: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(:constructor_arguments), do: "Constructor arguments do not match, please try again."
defp error_message(:name), do: "Wrong contract name, please try again." defp error_message(:name), do: "Wrong contract name, please try again."

@ -281,6 +281,7 @@ defmodule Explorer.Chain.TokenTransfer do
end end
@doc """ @doc """
Innventory tab query.
A token ERC-721 is considered unique because it corresponds to the possession A token ERC-721 is considered unique because it corresponds to the possession
of a specific asset. of a specific asset.
@ -293,9 +294,9 @@ defmodule Explorer.Chain.TokenTransfer do
tt in TokenTransfer, tt in TokenTransfer,
left_join: instance in Instance, left_join: instance in Instance,
on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id, 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], order_by: [desc: tt.block_number],
distinct: tt.token_id, distinct: [desc: tt.token_id],
preload: [:to_address], preload: [:to_address],
select: %{tt | instance: instance} 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 @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 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 @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 * `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction transaction
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer` * `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 | | `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 * `uncles` - uncle blocks where `forks` were collated
* `v` - The V field of the signature. * `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address` * `value` - wei transferred from `from_address` to `to_address`
* `revert_reason` - revert reason of transaction
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil, block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
@ -159,7 +161,8 @@ defmodule Explorer.Chain.Transaction do
to_address_hash: Hash.Address.t() | nil, to_address_hash: Hash.Address.t() | nil,
uncles: %Ecto.Association.NotLoaded{} | [Block.t()], uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
v: v(), v: v(),
value: Wei.t() value: Wei.t(),
revert_reason: String.t()
} }
@derive {Poison.Encoder, @derive {Poison.Encoder,
@ -199,6 +202,7 @@ defmodule Explorer.Chain.Transaction do
field(:status, Status) field(:status, Status)
field(:v, :decimal) field(:v, :decimal)
field(:value, Wei) field(:value, Wei)
field(:revert_reason, :string)
# A transient field for deriving old block hash during transaction upserts. # 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 # 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 require Logger
alias EthereumJSONRPC.Blocks
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range} alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash alias Explorer.Chain.Hash.Address, as: AddressHash
@ -33,6 +34,21 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end) end)
|> Enum.to_list() |> 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 = address_params =
balance_params balance_params
|> Stream.map(fn %{address_hash: hash} = map -> |> Stream.map(fn %{address_hash: hash} = map ->
@ -40,7 +56,11 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end) end)
|> Enum.to_list() |> 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) Chain.import(params)
end 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 status
to_address_hash to_address_hash
value value
revert_reason
)a )a
defp list_transactions(address_hash, max_block_number, options) do defp list_transactions(address_hash, max_block_number, options) do

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

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

@ -1,3 +1,4 @@
# credo:disable-for-this-file
defmodule Explorer.SmartContract.Verifier.ConstructorArguments do defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@moduledoc """ @moduledoc """
Smart contract contrstructor arguments verification logic. Smart contract contrstructor arguments verification logic.
@ -5,6 +6,13 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
alias ABI.{FunctionSelector, TypeDecoder} alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain 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 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") 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 defp extract_constructor_arguments(code, check_func, contract_source_code, contract_name) do
case code do case code do
# Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html # Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html
"a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments -> @metadata_hash_prefix_0_4_23 <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) 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 # Solidity >= 0.5.10 https://solidity.readthedocs.io/en/v0.5.10/metadata.html
"a265627a7a72305820" <> @metadata_hash_prefix_0_5_10 <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments -> <<_::binary-size(64)>> <>
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) @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 # 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:// # Metadata: Update the swarm hash to the current specification, changes bzzr0 to bzzr1 and urls to use bzz-raw://
"a265627a7a72315820" <> @metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments -> <<_::binary-size(64)>> <>
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) @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 # 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 # 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 # 0x00 0x32
# Note: there is a bug in the docs. Instead of 0x32, 0x33 should be used. # 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 # Fixing PR has been created https://github.com/ethereum/solidity/pull/8174
"a264697066735822" <> @metadata_hash_prefix_0_6_0 <>
<<_::binary-size(68)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0033" <> constructor_arguments -> <<_::binary-size(68)>> <>
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) @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.("") check_func.("")
@ -70,6 +237,35 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
end end
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 defp extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) do
constructor_arguments = constructor_arguments =
remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) 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 def find_all_requires(contract_source_code) do
if 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 -> 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, ");", parts: 2)
[require_content | _] = String.split(right_from_require_inside, ");", parts: 2)
[require_content | requires_list] [require_content | requires_list]
end) end)
else else

@ -98,10 +98,16 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
defp fetch_metadata(token_uri) do defp fetch_metadata(token_uri) do
case HTTPoison.get(token_uri) do case HTTPoison.get(token_uri) do
{:ok, %Response{body: body, status_code: 200}} -> {: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)
else
{:ok, json} = decode_json(body) {:ok, json} = decode_json(body)
check_type(json) check_type(json)
end
{:ok, %Response{body: body}} -> {:ok, %Response{body: body}} ->
{:error, body} {:error, body}

@ -84,7 +84,7 @@ defmodule Explorer.Mixfile do
# Code coverage # Code coverage
{:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"}, {:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"},
{:exvcr, "~> 0.10", only: :test}, {:exvcr, "~> 0.10", only: :test},
{:httpoison, "~> 1.0"}, {:httpoison, "~> 1.6"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false},
# Log errors and application output to separate files # 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", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" "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": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -26,9 +18,9 @@
} }
}, },
"command-exists": { "command-exists": {
"version": "1.2.8", "version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
"integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==" "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
}, },
"commander": { "commander": {
"version": "3.0.2", "version": "3.0.2",
@ -40,11 +32,6 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "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": { "fs-extra": {
"version": "0.30.0", "version": "0.30.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
@ -76,9 +63,9 @@
} }
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.3", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
@ -107,17 +94,6 @@
"graceful-fs": "^4.1.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": { "klaw": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
@ -139,11 +115,6 @@
"brace-expansion": "^1.1.7" "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": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -175,20 +146,15 @@
"glob": "^7.1.3" "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": { "semver": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}, },
"solc": { "solc": {
"version": "0.6.1", "version": "0.6.7",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.6.1.tgz", "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.7.tgz",
"integrity": "sha512-iKqNYps2p++x8L9sBg7JeAJb7EmW8VJ/2asAzwlLYcUhj86AzuWLe94UTSQHv1SSCCj/x6lya8twvXkZtlTbIQ==", "integrity": "sha512-a3iocjS1yGzw3Wy7jkqSLX3Vg1lMDCyoKZoVfpOagRGWkh37f11BrcUDO8f73rjdpw2WUBSLJtTQ26i52/0JOg==",
"requires": { "requires": {
"command-exists": "^1.2.8", "command-exists": "^1.2.8",
"commander": "3.0.2", "commander": "3.0.2",

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

@ -1,13 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
var sourceCodePath = process.argv[2]; var sourceCodePath = process.argv[2];
var version = process.argv[3]; var compilerVersionPath = process.argv[3];
var optimize = process.argv[4]; var optimize = process.argv[4];
var optimizationRuns = parseInt(process.argv[5], 10); var optimizationRuns = parseInt(process.argv[5], 10);
var newContractName = process.argv[6]; var newContractName = process.argv[6];
var externalLibraries = JSON.parse(process.argv[7]) var externalLibraries = JSON.parse(process.argv[7])
var evmVersion = process.argv[8]; var evmVersion = process.argv[8];
var compilerVersionPath = process.argv[9];
var solc = require('solc') var solc = require('solc')
var compilerSnapshot = require(compilerVersionPath); 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() value_fetched_at: DateTime.utc_now()
} }
run_changes(new_changes, options) |> IO.inspect() run_changes(new_changes, options)
end end
end end

@ -156,6 +156,12 @@ defmodule Explorer.Chain.TokenTransferTest do
|> insert() |> insert()
|> with_block(insert(:block, number: 1)) |> with_block(insert(:block, number: 1))
insert(
:token_instance,
token_id: 42,
token_contract_address_hash: token_contract_address.hash
)
insert( insert(
:token_transfer, :token_transfer,
to_address: build(:address), to_address: build(:address),
@ -168,7 +174,7 @@ defmodule Explorer.Chain.TokenTransferTest do
another_transaction = another_transaction =
:transaction :transaction
|> insert() |> insert()
|> with_block(insert(:block, number: 2)) |> with_block(insert(:block, number: 3))
last_owner = last_owner =
insert( 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) 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: 1000, block_number: block.number)
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: block_one_day_ago.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) balances = Chain.address_to_balances_by_day(address.hash)
@ -4735,6 +4737,7 @@ defmodule Explorer.ChainTest do
yesterday = Timex.shift(noon, days: -1) yesterday = Timex.shift(noon, days: -1)
block_one_day_ago = insert(:block, timestamp: yesterday) 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, 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) 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) 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, 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) [balance] = Chain.address_to_balances_by_day(address.hash)
@ -5021,32 +5025,6 @@ defmodule Explorer.ChainTest do
end end
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 describe "extract_db_name/1" do
test "extracts correct db name" do test "extracts correct db name" do
db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1" db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1"
@ -5187,4 +5165,32 @@ defmodule Explorer.ChainTest do
} }
end end
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 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"]) 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)
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) 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.CurrentTokenBalance,
Address.TokenBalance, Address.TokenBalance,
Address.CoinBalance, Address.CoinBalance,
Address.CoinBalanceDaily,
Block, Block,
ContractMethod, ContractMethod,
Data, Data,
@ -56,6 +57,13 @@ defmodule Explorer.Factory do
} }
end 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 def update_balance_value(%CoinBalance{address_hash: address_hash, block_number: block_number}, value) do
Repo.update_all( Repo.update_all(
from( from(
@ -71,6 +79,11 @@ defmodule Explorer.Factory do
|> struct!(value: Enum.random(1..100_000)) |> struct!(value: Enum.random(1..100_000))
end end
def fetched_balance_daily_factory do
unfetched_balance_daily_factory()
|> struct!(value: Enum.random(1..100_000))
end
def contract_address_factory do def contract_address_factory do
%Address{ %Address{
hash: address_hash(), hash: address_hash(),
@ -82,6 +95,8 @@ defmodule Explorer.Factory do
%{ %{
bytecode: bytecode:
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820f65a3adc1cfb055013d1dc37d0fe98676e2a5963677fa7541a10386d163446680029", "0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820f65a3adc1cfb055013d1dc37d0fe98676e2a5963677fa7541a10386d163446680029",
tx_input:
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820853a985d0a4b20246785fc2f0357c202faa3db289980a48737180f358f9ddc3c0029",
name: "SimpleStorage", name: "SimpleStorage",
source_code: """ source_code: """
pragma solidity ^0.4.24; 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", "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; }}", "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", "expected_bytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158203e59bfb9a5a2e55d38231922c86d8b2ec9b66cb2f6595613674bc4e15290b60764736f6c634300050b0032",
"tx_input": "6080604052600a60005534801561001557600080fd5b50610169806100256000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806360fe47b1146100465780636d4ce63c146100655780637cf5dab01461007f575b600080fd5b6100636004803603602081101561005c57600080fd5b503561009c565b005b61006d6100a1565b60408051918252519081900360200190f35b6100636004803603602081101561009557600080fd5b50356100a7565b600055565b60005490565b600054733662e222908fa35f013bee37695d0510098b6d7363771602f79091836040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561010257600080fd5b505af4158015610116573d6000803e3d6000fd5b505050506040513d602081101561012c57600080fd5b50516000555056fea265627a7a723158207809bc828bbcd3de3e6b6483facb0c5902901fc8827283c749c8ea0702eb871f64736f6c634300050b0032",
"external_libraries": { "external_libraries": {
"BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73" "BadSafeMath": "0x3662e222908fa35f013bee37695d0510098b6d73"
}, },
@ -10,4 +11,3 @@
"optimize": true "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.{ alias Indexer.Transform.{
AddressCoinBalances, AddressCoinBalances,
AddressCoinBalancesDaily,
Addresses, Addresses,
AddressTokenBalances, AddressTokenBalances,
MintTransfers, MintTransfers,
@ -55,6 +56,7 @@ defmodule Indexer.Block.Fetcher do
address_hash_to_fetched_balance_block_number: address_hash_to_fetched_balance_block_number, address_hash_to_fetched_balance_block_number: address_hash_to_fetched_balance_block_number,
addresses: Import.Runner.options(), addresses: Import.Runner.options(),
address_coin_balances: Import.Runner.options(), address_coin_balances: Import.Runner.options(),
address_coin_balances_daily: Import.Runner.options(),
address_token_balances: Import.Runner.options(), address_token_balances: Import.Runner.options(),
blocks: Import.Runner.options(), blocks: Import.Runner.options(),
block_second_degree_relations: Import.Runner.options(), block_second_degree_relations: Import.Runner.options(),
@ -153,6 +155,14 @@ defmodule Indexer.Block.Fetcher do
transactions_params: transactions_with_receipts transactions_params: transactions_with_receipts
} }
|> AddressCoinBalances.params_set(), |> 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 <- beneficiaries_with_gas_payment <-
beneficiary_params_set beneficiary_params_set
|> add_gas_payments(transactions_with_receipts, blocks) |> add_gas_payments(transactions_with_receipts, blocks)
@ -164,6 +174,7 @@ defmodule Indexer.Block.Fetcher do
%{ %{
addresses: %{params: addresses}, addresses: %{params: addresses},
address_coin_balances: %{params: coin_balances_params_set}, 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}, address_token_balances: %{params: address_token_balances},
blocks: %{params: blocks}, blocks: %{params: blocks},
block_second_degree_relations: %{params: block_second_degree_relations_params}, block_second_degree_relations: %{params: block_second_degree_relations_params},

@ -26,7 +26,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
] ]
alias Ecto.Changeset alias Ecto.Changeset
alias EthereumJSONRPC.{FetchedBalances, Subscription} alias EthereumJSONRPC.{Blocks, FetchedBalances, Subscription}
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Cache.Accounts alias Explorer.Chain.Cache.Accounts
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
@ -172,17 +172,25 @@ defmodule Indexer.Block.Realtime.Fetcher do
block_fetcher, block_fetcher,
%{ %{
address_coin_balances: %{params: address_coin_balances_params}, 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, address_hash_to_fetched_balance_block_number: address_hash_to_block_number,
addresses: %{params: addresses_params}, addresses: %{params: addresses_params},
block_rewards: block_rewards block_rewards: block_rewards
} = options } = options
) do ) 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,
balances(block_fetcher, %{ balances(block_fetcher, %{
address_hash_to_block_number: address_hash_to_block_number, address_hash_to_block_number: address_hash_to_block_number,
addresses_params: addresses_params, 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), {block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors),
chain_import_options = chain_import_options =
@ -191,7 +199,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
|> put_in([:addresses, :params], balances_addresses_params) |> put_in([:addresses, :params], balances_addresses_params)
|> put_in([:blocks, :params, Access.all(), :consensus], true) |> put_in([:blocks, :params, Access.all(), :consensus], true)
|> put_in([:block_rewards], chain_import_block_rewards) |> 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 {:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do
async_import_remaining_block_data( async_import_remaining_block_data(
imported, 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)) 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, _} = error ->
error error

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

@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance do
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
alias EthereumJSONRPC.FetchedBalances alias EthereumJSONRPC.{Blocks, FetchedBalances}
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.{Block, Hash}
alias Explorer.Chain.Cache.Accounts alias Explorer.Chain.Cache.Accounts
@ -77,17 +77,22 @@ defmodule Indexer.Fetcher.CoinBalance do
# `{address, block}`, so take unique params only # `{address, block}`, so take unique params only
unique_entries = Enum.uniq(entries) 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.metadata(count: unique_entry_count)
Logger.debug(fn -> "fetching" end) Logger.debug(fn -> "fetching" end)
unique_entries unique_filtered_entries
|> Enum.map(&entry_to_params/1) |> Enum.map(&entry_to_params/1)
|> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments) |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments)
|> case do |> case do
{:ok, fetched_balances} -> {:ok, fetched_balances} ->
run_fetched_balances(fetched_balances, unique_entries) run_fetched_balances(fetched_balances, unique_filtered_entries)
{:error, reason} -> {:error, reason} ->
Logger.error( Logger.error(
@ -97,7 +102,16 @@ defmodule Indexer.Fetcher.CoinBalance do
error_count: unique_entry_count 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
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)) 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) addresses_params = balances_params_to_address_params(importable_balances_params)
Chain.import(%{ Chain.import(%{
addresses: %{params: addresses_params, with: :balance_changeset}, addresses: %{params: addresses_params, with: :balance_changeset},
address_coin_balances: %{params: importable_balances_params}, 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 broadcast: broadcast_type
}) })
end end

@ -15,10 +15,10 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2]
import EthereumJSONRPC, only: [integer_to_quantity: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias EthereumJSONRPC.FetchedBalances alias EthereumJSONRPC.{FetchedBalances}
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.Address alias Explorer.Chain.Address
alias Explorer.Chain.Address.CoinBalance alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily}
alias Explorer.Chain.Cache.{Accounts, BlockNumber} alias Explorer.Chain.Cache.{Accounts, BlockNumber}
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher
@ -86,6 +86,12 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
{:noreply, state} {:noreply, state}
end 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 ## Implementation
defp do_trigger_fetch(%Address{fetched_coin_balance_block_number: nil} = address, latest_block_number, _) do 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 end
defp do_trigger_fetch(address, latest_block_number, stale_balance_window) do 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 = latest =
from( from(
cb in CoinBalance, cb in CoinBalance,
@ -105,15 +119,28 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
limit: 1 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 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}) GenServer.cast(__MODULE__, {:fetch_and_update, latest_block_number, address})
{:stale, latest_block_number} {:stale, latest_block_number}
else else
case Repo.one(latest) do case Repo.one(query_balances) do
nil -> nil ->
# There is no recent coin balance to fetch, so we check to see how old the # 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. # 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 :current
%CoinBalance{value_fetched_at: nil, block_number: block_number} -> %CoinBalance{value_fetched_at: nil, block_number: block_number} ->
@ -122,11 +149,19 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
{:pending, block_number} {:pending, block_number}
%CoinBalance{} -> %CoinBalance{} ->
do_trigger_balance_daily_fetch_query(address, latest_block_number, query_balances_daily)
:current :current
end end
end 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 defp fetch_and_import(block_number, address, json_rpc_named_arguments) do
case fetch_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(fetched_balances) {:ok, fetched_balances} -> do_import(fetched_balances)
@ -134,6 +169,13 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end end
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 defp fetch_and_update(block_number, address, json_rpc_named_arguments) do
case fetch_balances(block_number, address, json_rpc_named_arguments) do case fetch_balances(block_number, address, json_rpc_named_arguments) do
{:ok, %{params_list: []}} -> {:ok, %{params_list: []}} ->
@ -165,6 +207,13 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end end
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 defp latest_block_number do
BlockNumber.get_max() BlockNumber.get_max()
end 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) block_quantity = integer_to_quantity(block_number)
miner_hash = "0x0000000000000000000000000000000000000000" miner_hash = "0x0000000000000000000000000000000000000000"
res = eth_block_number_fake_response(block_quantity)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity -> EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox EthereumJSONRPC.Mox
@ -137,6 +139,17 @@ defmodule Indexer.Block.FetcherTest do
{:ok, [%{id: id, result: []}]} {:ok, [%{id: id, result: []}]}
end end
end) end)
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
params: [^block_quantity, true]
}
],
_ ->
{:ok, [res]}
end)
EthereumJSONRPC.Geth -> EthereumJSONRPC.Geth ->
EthereumJSONRPC.Mox EthereumJSONRPC.Mox
@ -381,10 +394,78 @@ defmodule Indexer.Block.FetcherTest do
end) end)
# async requests need to be grouped in one expect because the order is non-deterministic while multiple expect # 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 # 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 [request] = json
case request do 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]} -> %{id: id, method: "eth_getBalance", params: [^to_address_hash, ^block_quantity]} ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x1"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x1"}]}
@ -662,6 +743,22 @@ defmodule Indexer.Block.FetcherTest do
} }
%{id: id, method: "trace_block"} -> %{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, id: id,
result: [ result: [
@ -742,4 +839,40 @@ defmodule Indexer.Block.FetcherTest do
counts.buffer == 0 and counts.tasks == 0 counts.buffer == 0 and counts.tasks == 0
end) end)
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 end

@ -205,7 +205,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do
} }
]} ]}
end) end)
|> expect(:json_rpc, 2, fn |> expect(:json_rpc, 5, fn
[ [
%{id: 0, jsonrpc: "2.0", method: "trace_block", params: ["0x3C365F"]}, %{id: 0, jsonrpc: "2.0", method: "trace_block", params: ["0x3C365F"]},
%{id: 1, jsonrpc: "2.0", method: "trace_block", params: ["0x3C3660"]} %{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: 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, id: 0,

@ -117,6 +117,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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.Block.Reward) == 0
parent = self() parent = self()
@ -190,6 +205,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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() parent = self()
pid = pid =
@ -320,6 +350,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0 assert count(Chain.Address.CoinBalance) == 0
@ -408,6 +453,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0 assert count(Chain.Address.CoinBalance) == 0
@ -486,6 +546,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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.Block.Reward) == 1
assert count(Chain.Address.CoinBalance) == 1 assert count(Chain.Address.CoinBalance) == 1
@ -625,6 +700,21 @@ defmodule Indexer.Fetcher.BlockRewardTest do
} }
end) 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.Block.Reward) == 0
assert count(Chain.Address.CoinBalance) == 0 assert count(Chain.Address.CoinBalance) == 0
@ -684,4 +774,40 @@ defmodule Indexer.Fetcher.BlockRewardTest do
do_wait_until(parent, ref, producer) do_wait_until(parent, ref, producer)
end end
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 end

@ -117,6 +117,21 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end) 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} assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101}
{:ok, expected_wei} = Wei.cast(2) {:ok, expected_wei} = Wei.cast(2)
@ -144,6 +159,20 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end) 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} assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 102}
{:ok, expected_wei} = Wei.cast(2) {:ok, expected_wei} = Wei.cast(2)
@ -154,4 +183,40 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
) )
end end
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 end

@ -58,6 +58,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options -> _options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]} {:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end) 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 end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data) {:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
@ -114,6 +129,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options -> _options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]} {:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end) 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 end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data) {:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
@ -178,6 +208,21 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
_options -> _options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]} {:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end) 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 end
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) 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) {:ok, %Hash{bytes: address_hash_bytes}} = Hash.Address.cast(hash_data)
entries = Enum.map(block_quantities, &{address_hash_bytes, quantity_to_integer(&1)}) 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 case CoinBalance.run(entries, json_rpc_named_arguments) do
:ok -> :ok ->
balances = Repo.all(from(balance in Address.CoinBalance, where: balance.address_hash == ^hash_data)) 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 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() %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: [_, _]}], _ -> expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, method: "eth_getBalance", params: [_, _]}], _ ->
{:ok, [%{id: id, result: "0x1"}]} {:ok, [%{id: id, result: "0x1"}]}
end) 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) assert :ok = CoinBalance.run(entries, json_rpc_named_arguments)
end 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() %Hash{bytes: address_hash_bytes} = address_hash()
bad_block_number = block_number() bad_block_number = block_number()
good_block_number = block_number() good_block_number = block_number()
@ -359,6 +440,22 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
{:ok, responses} {:ok, responses}
end) 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}]} = assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} =
CoinBalance.run( CoinBalance.run(
[{address_hash_bytes, good_block_number}, {address_hash_bytes, bad_block_number}], [{address_hash_bytes, good_block_number}, {address_hash_bytes, bad_block_number}],
@ -374,4 +471,40 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
Process.sleep(100) Process.sleep(100)
wait(producer) wait(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 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": {: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"}, "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"}, "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": {: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_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"}, "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"}, "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"}, "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"}, "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"}, "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"}, "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"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"},

Loading…
Cancel
Save