Merge branch 'master' into fix-docker

pull/1531/head
Victor Baranov 6 years ago committed by GitHub
commit 19f69cb094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 94
      .circleci/config.yml
  2. 3
      .dialyzer-ignore
  3. 2
      .tool-versions
  4. 165
      CHANGELOG.md
  5. 1
      CONTRIBUTING.md
  6. 17
      PULL_REQUEST_TEMPLATE.md
  7. 11
      README.md
  8. 16
      apps/block_scout_web/assets/css/_code.scss
  9. 2
      apps/block_scout_web/assets/js/app.js
  10. 10
      apps/block_scout_web/assets/js/lib/from_now.js
  11. 26
      apps/block_scout_web/assets/js/pages/admin/tasks.js
  12. 18
      apps/block_scout_web/config/config.exs
  13. 2
      apps/block_scout_web/config/test.exs
  14. 2
      apps/block_scout_web/lib/block_scout_web.ex
  15. 4
      apps/block_scout_web/lib/block_scout_web/admin_router.ex
  16. 22
      apps/block_scout_web/lib/block_scout_web/chain.ex
  17. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  18. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  19. 32
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  20. 30
      apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
  21. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  22. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  23. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  24. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  25. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  26. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  27. 22
      apps/block_scout_web/lib/block_scout_web/controllers/admin/tasks_controller.ex
  28. 44
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  29. 194
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  30. 42
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helpers.ex
  31. 49
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
  32. 16
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  33. 79
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
  34. 17
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  35. 4
      apps/block_scout_web/lib/block_scout_web/csp_header.ex
  36. 353
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  37. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  38. 11
      apps/block_scout_web/lib/block_scout_web/router.ex
  39. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex
  40. 20
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  41. 44
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  42. 12
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  43. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/_metatags.html.eex
  44. 30
      apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex
  45. 37
      apps/block_scout_web/lib/block_scout_web/templates/admin/dashboard/index.html.eex
  46. 2
      apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex
  47. 9
      apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
  48. 30
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  49. 15
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  50. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex
  51. 67
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  52. 44
      apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
  53. 21
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  54. 20
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  55. 116
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  56. 14
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  57. 10
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  58. 89
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  59. 9
      apps/block_scout_web/mix.exs
  60. 263
      apps/block_scout_web/priv/gettext/default.pot
  61. 263
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  62. 14
      apps/block_scout_web/test/block_scout_web/chain_test.exs
  63. 74
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  64. 343
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  65. 16
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs
  66. 56
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  67. 99
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs
  68. 22
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  69. 13
      apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs
  70. 8
      apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex
  71. 11
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  72. 2
      apps/block_scout_web/test/block_scout_web/schema/query/address_test.exs
  73. 2
      apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs
  74. 74
      apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs
  75. 26
      apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs
  76. 24
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  77. 13
      apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json
  78. 2015
      apps/block_scout_web/test/support/fixture/smart_contract/solc_bin.json
  79. 8
      apps/ethereum_jsonrpc/config/config.exs
  80. 9
      apps/ethereum_jsonrpc/config/test.exs
  81. 60
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  82. 30
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex
  83. 3
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
  84. 95
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  85. 65
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
  86. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex
  87. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  88. 3
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
  89. 70
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  90. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  91. 91
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
  92. 42
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  93. 3
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
  94. 16
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex
  95. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex
  96. 11
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket/web_socket_client.ex
  97. 12
      apps/ethereum_jsonrpc/lib/rsk.ex
  98. 4
      apps/ethereum_jsonrpc/mix.exs
  99. 134
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs
  100. 133
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,7 +3,7 @@ jobs:
build:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2-node-browsers
- image: circleci/elixir:1.8.1-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@ -17,6 +17,9 @@ jobs:
- run: sudo apt-get update; sudo apt-get -y install autoconf build-essential libgmp3-dev libtool
- checkout
- run:
command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400
- run: mix local.hex --force
- run: mix local.rebar --force
@ -30,17 +33,17 @@ jobs:
- restore_cache:
keys:
- v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run: mix deps.get
- restore_cache:
keys:
- v6-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
- v6-npm-install-{{ .Branch }}
- v6-npm-install
- v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
- v7-npm-install-{{ .Branch }}
- v7-npm-install
- run:
command: npm install
@ -61,13 +64,13 @@ jobs:
working_directory: "apps/block_scout_web/assets"
- save_cache:
key: v6-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
key: v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
paths: "apps/block_scout_web/assets/node_modules"
- save_cache:
key: v6-npm-install-{{ .Branch }}
key: v7-npm-install-{{ .Branch }}
paths: "apps/block_scout_web/assets/node_modules"
- save_cache:
key: v6-npm-install
key: v7-npm-install
paths: "apps/block_scout_web/assets/node_modules"
- run: mix compile
@ -80,17 +83,17 @@ jobs:
# `deps` needs to be cached with `_build` because `_build` will symlink into `deps`
- save_cache:
key: v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
key: v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths:
- deps
- _build
- save_cache:
key: v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
key: v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
paths:
- deps
- _build
- save_cache:
key: v6-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
key: v7-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
paths:
- deps
- _build
@ -126,7 +129,7 @@ jobs:
check_formatted:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -140,7 +143,7 @@ jobs:
credo:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -174,7 +177,7 @@ jobs:
dialyzer:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -188,9 +191,9 @@ jobs:
- restore_cache:
keys:
- v6-mix-dailyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v6-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v6-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run:
name: Unpack PLT cache
@ -210,15 +213,15 @@ jobs:
cp ~/.mix/dialyxir*.plt plts/
- save_cache:
key: v6-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths:
- plts
- save_cache:
key: v6-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
paths:
- plts
- save_cache:
key: v6-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
paths:
- plts
@ -244,7 +247,7 @@ jobs:
gettext:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -283,7 +286,7 @@ jobs:
release:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: prod
@ -309,7 +312,7 @@ jobs:
sobelow:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -333,7 +336,7 @@ jobs:
test_geth_http_websocket:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2-node-browsers
- image: circleci/elixir:1.8.1-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@ -357,6 +360,10 @@ jobs:
- attach_workspace:
at: .
- run:
command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400
- run: mix local.hex --force
- run: mix local.rebar --force
@ -383,7 +390,7 @@ jobs:
test_geth_mox:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2-node-browsers
- image: circleci/elixir:1.8.1-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@ -407,6 +414,10 @@ jobs:
- attach_workspace:
at: .
- run:
command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400
- run: mix local.hex --force
- run: mix local.rebar --force
@ -433,7 +444,7 @@ jobs:
test_parity_http_websocket:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2-node-browsers
- image: circleci/elixir:1.8.1-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@ -457,6 +468,10 @@ jobs:
- attach_workspace:
at: .
- run:
command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400
- run: mix local.hex --force
- run: mix local.rebar --force
@ -483,7 +498,7 @@ jobs:
test_parity_mox:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2-node-browsers
- image: circleci/elixir:1.8.1-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@ -507,6 +522,10 @@ jobs:
- attach_workspace:
at: .
- run:
command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400
- run: mix local.hex --force
- run: mix local.rebar --force
@ -533,7 +552,7 @@ jobs:
coveralls_merge:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.7.2
- image: circleci/elixir:1.8.1
environment:
MIX_ENV: test
@ -552,6 +571,9 @@ workflows:
# This unfortunately will only fire if all the tests pass because of how `requires` works
- coveralls_merge:
requires:
- test_parity_http_websocket
- test_parity_mox
- test_geth_http_websocket
- test_geth_mox
- credo:
requires:
@ -569,7 +591,8 @@ workflows:
- eslint
- jest
- sobelow
# This makes these synchronous, instead of asynchronous
- test_parity_http_websocket
- test_parity_mox
- test_geth_http_websocket
- test_geth_mox
- dialyzer:
@ -595,13 +618,10 @@ workflows:
- build
- test_parity_mox:
requires:
# This makes these synchronous, instead of asynchronous
- test_parity_http_websocket
- build
- test_geth_http_websocket:
requires:
# This makes these synchronous, instead of asynchronous
- test_parity_mox
- build
- test_geth_mox:
requires:
# This makes these synchronous, instead of asynchronous
- test_geth_http_websocket
- build

@ -1,3 +1,6 @@
:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer()

@ -1,3 +1,3 @@
elixir 1.7.1
elixir 1.8.1
erlang 21.0.4
nodejs 10.11.0

@ -0,0 +1,165 @@
## Current
### Features
- [#1739](https://github.com/poanetwork/blockscout/pull/1739) - highlight decompiled source code
- [#1696](https://github.com/poanetwork/blockscout/pull/1696) - full-text search by tokens
- [#1742](https://github.com/poanetwork/blockscout/pull/1742) - Support RSK
- [#1777](https://github.com/poanetwork/blockscout/pull/1777) - show ERC-20 token transfer info on transaction page
- [#1770](https://github.com/poanetwork/blockscout/pull/1770) - set a websocket keepalive from config
- [#1789](https://github.com/poanetwork/blockscout/pull/1789) - add ERC-721 info to transaction overview page
### Fixes
- [#1724](https://github.com/poanetwork/blockscout/pull/1724) - Remove internal tx and token balance fetching from realtime fetcher
- [#1727](https://github.com/poanetwork/blockscout/pull/1727) - add logs pagination in rpc api
- [#1740](https://github.com/poanetwork/blockscout/pull/1740) - fix empty block time
- [#1743](https://github.com/poanetwork/blockscout/pull/1743) - sort decompiled smart contracts in lexicographical order
- [#1756](https://github.com/poanetwork/blockscout/pull/1756) - add today's token balance from the previous value
- [#1769](https://github.com/poanetwork/blockscout/pull/1769) - add timestamp to block overview
- [#1768](https://github.com/poanetwork/blockscout/pull/1768) - fix first block parameter
- [#1778](https://github.com/poanetwork/blockscout/pull/1778) - Make websocket optional for realtime fetcher
- [#1795](https://github.com/poanetwork/blockscout/pull/1795) - fix line numbers for decompiled contracts
### Chore
- [#1783](https://github.com/poanetwork/blockscout/pull/1783) - Update README with the chains that use Blockscout
- [#1780](https://github.com/poanetwork/blockscout/pull/1780) - Update link to the Github repo in the footer
- [#1757](https://github.com/poanetwork/blockscout/pull/1757) - Change twitter acc link to official Blockscout acc twitter
- [#1749](https://github.com/poanetwork/blockscout/pull/1749) - Replace the link in the footer with the official POA announcements tg channel link
- [#1718](https://github.com/poanetwork/blockscout/pull/1718) - Flatten indexer module hierarchy and supervisor tree
- [#1753](https://github.com/poanetwork/blockscout/pull/1753) - Add a check mark to decompiled contract tab
- [#1744](https://github.com/poanetwork/blockscout/pull/1744) - remove `0x0..0` from tests
- [#1763](https://github.com/poanetwork/blockscout/pull/1763) - Describe indexer structure and list existing fetchers
## 1.3.9-beta
### Features
- [#1662](https://github.com/poanetwork/blockscout/pull/1662) - allow specifying number of optimization runs
- [#1654](https://github.com/poanetwork/blockscout/pull/1654) - add decompiled code tab
- [#1661](https://github.com/poanetwork/blockscout/pull/1661) - try to compile smart contract with the latest evm version
- [#1665](https://github.com/poanetwork/blockscout/pull/1665) - Add contract verification RPC endpoint.
- [#1706](https://github.com/poanetwork/blockscout/pull/1706) - allow setting update interval for addresses with b
### Fixes
- [#1669](https://github.com/poanetwork/blockscout/pull/1669) - do not fail if multiple matching tokens are found
- [#1691](https://github.com/poanetwork/blockscout/pull/1691) - decrease token metadata update interval
- [#1688](https://github.com/poanetwork/blockscout/pull/1688) - do not fail if failure reason is atom
- [#1692](https://github.com/poanetwork/blockscout/pull/1692) - exclude decompiled smart contract from encoding
- [#1684](https://github.com/poanetwork/blockscout/pull/1684) - Discard child block with parent_hash not matching hash of imported block
- [#1699](https://github.com/poanetwork/blockscout/pull/1699) - use seconds as transaction cache period measure
- [#1697](https://github.com/poanetwork/blockscout/pull/1697) - fix failing in rpc if balance is empty
- [#1711](https://github.com/poanetwork/blockscout/pull/1711) - rescue failing repo in block number cache update
- [#1712](https://github.com/poanetwork/blockscout/pull/1712) - do not set contract code from transaction input
- [#1714](https://github.com/poanetwork/blockscout/pull/1714) - fix average block time calculation
### Chore
- [#1693](https://github.com/poanetwork/blockscout/pull/1693) - Add a checklist to the PR template
## 1.3.8-beta
### Features
- [#1611](https://github.com/poanetwork/blockscout/pull/1611) - allow setting the first indexing block
- [#1596](https://github.com/poanetwork/blockscout/pull/1596) - add endpoint to create decompiled contracts
- [#1634](https://github.com/poanetwork/blockscout/pull/1634) - add transaction count cache
### Fixes
- [#1630](https://github.com/poanetwork/blockscout/pull/1630) - (Fix) colour for release link in the footer
- [#1621](https://github.com/poanetwork/blockscout/pull/1621) - Modify query to fetch failed contract creations
- [#1614](https://github.com/poanetwork/blockscout/pull/1614) - Do not fetch burn address token balance
- [#1639](https://github.com/poanetwork/blockscout/pull/1614) - Optimize token holder count updates when importing address current balances
- [#1643](https://github.com/poanetwork/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks
- [#1647](https://github.com/poanetwork/blockscout/pull/1647) - Fix typo in view
- [#1650](https://github.com/poanetwork/blockscout/pull/1650) - Add petersburg evm version to smart contract verifier
- [#1657](https://github.com/poanetwork/blockscout/pull/1657) - Force consensus loss for parent block if its hash mismatches parent_hash
### Chore
## 1.3.7-beta
### Features
### Fixes
- [#1615](https://github.com/poanetwork/blockscout/pull/1615) - Add more logging to code fixer process
- [#1613](https://github.com/poanetwork/blockscout/pull/1613) - Fix USD fee value
- [#1577](https://github.com/poanetwork/blockscout/pull/1577) - Add process to fix contract with code
- [#1583](https://github.com/poanetwork/blockscout/pull/1583) - Chunk JSON-RPC batches in case connection times out
### Chore
- [#1610](https://github.com/poanetwork/blockscout/pull/1610) - Add PIRL to Readme
## 1.3.6-beta
### Features
- [#1589](https://github.com/poanetwork/blockscout/pull/1589) - RPC endpoint to list addresses
- [#1567](https://github.com/poanetwork/blockscout/pull/1567) - Allow setting different configuration just for realtime fetcher
- [#1562](https://github.com/poanetwork/blockscout/pull/1562) - Add incoming transactions count to contract view
- [#1608](https://github.com/poanetwork/blockscout/pull/1608) - Add listcontracts RPC Endpoint
### Fixes
- [#1595](https://github.com/poanetwork/blockscout/pull/1595) - Reduce block_rewards in the catchup fetcher
- [#1590](https://github.com/poanetwork/blockscout/pull/1590) - Added guard for fetching blocks with invalid number
- [#1588](https://github.com/poanetwork/blockscout/pull/1588) - Fix usd value on address page
- [#1586](https://github.com/poanetwork/blockscout/pull/1586) - Exact timestamp display
- [#1581](https://github.com/poanetwork/blockscout/pull/1581) - Consider `creates` param when fetching transactions
- [#1559](https://github.com/poanetwork/blockscout/pull/1559) - Change v column type for Transactions table
### Chore
- [#1579](https://github.com/poanetwork/blockscout/pull/1579) - Add SpringChain to the list of Additional Chains Utilizing BlockScout
- [#1578](https://github.com/poanetwork/blockscout/pull/1578) - Refine contributing procedure
- [#1572](https://github.com/poanetwork/blockscout/pull/1572) - Add option to disable block rewards in indexer config
## 1.3.5-beta
### Features
- [#1560](https://github.com/poanetwork/blockscout/pull/1560) - Allow executing smart contract functions in arbitrarily sized batches
- [#1543](https://github.com/poanetwork/blockscout/pull/1543) - Use trace_replayBlockTransactions API for faster tracing
- [#1558](https://github.com/poanetwork/blockscout/pull/1558) - Allow searching by token symbol
- [#1551](https://github.com/poanetwork/blockscout/pull/1551) Exact date and time for Transaction details page
- [#1547](https://github.com/poanetwork/blockscout/pull/1547) - Verify smart contracts with evm versions
- [#1540](https://github.com/poanetwork/blockscout/pull/1540) - Fetch ERC721 token balances if sender is '0x0..0'
- [#1539](https://github.com/poanetwork/blockscout/pull/1539) - Add the link to release in the footer
- [#1519](https://github.com/poanetwork/blockscout/pull/1519) - Create contract methods
- [#1496](https://github.com/poanetwork/blockscout/pull/1496) - Remove dropped/replaced transactions in pending transactions list
- [#1492](https://github.com/poanetwork/blockscout/pull/1492) - Disable usd value for an empty exchange rate
- [#1466](https://github.com/poanetwork/blockscout/pull/1466) - Decoding candidates for unverified contracts
### Fixes
- [#1545](https://github.com/poanetwork/blockscout/pull/1545) - Fix scheduling of latest block polling in Realtime Fetcher
- [#1554](https://github.com/poanetwork/blockscout/pull/1554) - Encode integer parameters when calling smart contract functions
- [#1537](https://github.com/poanetwork/blockscout/pull/1537) - Fix test that depended on date
- [#1534](https://github.com/poanetwork/blockscout/pull/1534) - Render a nicer error when creator cannot be determined
- [#1527](https://github.com/poanetwork/blockscout/pull/1527) - Add index to value_fetched_at
- [#1518](https://github.com/poanetwork/blockscout/pull/1518) - Select only distinct failed transactions
- [#1516](https://github.com/poanetwork/blockscout/pull/1516) - Fix coin balance params reducer for pending transaction
- [#1511](https://github.com/poanetwork/blockscout/pull/1511) - Set correct log level for production
- [#1510](https://github.com/poanetwork/blockscout/pull/1510) - Fix test that fails every 1st day of the month
- [#1509](https://github.com/poanetwork/blockscout/pull/1509) - Add index to blocks' consensus
- [#1508](https://github.com/poanetwork/blockscout/pull/1508) - Remove duplicated indexes
- [#1505](https://github.com/poanetwork/blockscout/pull/1505) - Use https instead of ssh for absinthe libs
- [#1501](https://github.com/poanetwork/blockscout/pull/1501) - Constructor_arguments must be type `text`
- [#1498](https://github.com/poanetwork/blockscout/pull/1498) - Add index for created_contract_address_hash in transactions
- [#1493](https://github.com/poanetwork/blockscout/pull/1493) - Do not do work in process initialization
- [#1487](https://github.com/poanetwork/blockscout/pull/1487) - Limit geth sync to 128 blocks
- [#1484](https://github.com/poanetwork/blockscout/pull/1484) - Allow decoding input as utf-8
- [#1479](https://github.com/poanetwork/blockscout/pull/1479) - Remove smoothing from coin balance chart
### Chore
- [https://github.com/poanetwork/blockscout/pull/1532](https://github.com/poanetwork/blockscout/pull/1532) - Upgrade elixir to 1.8.1
- [https://github.com/poanetwork/blockscout/pull/1553](https://github.com/poanetwork/blockscout/pull/1553) - Dockerfile: remove 1.7.1 version pin FROM bitwalker/alpine-elixir-phoenix
- [https://github.com/poanetwork/blockscout/pull/1465](https://github.com/poanetwork/blockscout/pull/1465) - Resolve lodash security alert

@ -6,6 +6,7 @@
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create a new Pull Request
7. Update CHANGELOG.md with the link to PR and description of the changes
### General

@ -18,3 +18,20 @@
## Upgrading
*If you have any Incompatible Changes in the above Changelog, outline how users of prior versions can upgrade once this PR lands or when reviewers are testing locally. A common upgrading step is "Database reset and re-index required".*
## Checklist for your PR
<!--
Ideally a PR has all of the checkmarks set.
If something in this list is irrelevant to your PR, you should still set this
checkmark indicating that you are sure it is dealt with (be that by irrelevance).
If you don't set a checkmark (e. g. don't add a test for new functionality),
you must be able to justify that.
-->
- [ ] I added an entry to `CHANGELOG.md` with this PR
- [ ] If I added new functionality, I added tests covering it.
- [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again.
- [ ] I checked whether I should update the docs and did so if necessary

@ -52,6 +52,9 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
* [Goerli Testnet](https://blockscout.com/eth/goerli)
* [Rinkeby Testnet](https://blockscout.com/eth/rinkeby)
* [Ethereum Classic](https://blockscout.com/etc/mainnet)
* [Aerum](https://blockscout.com/aerum/mainnet)
* [Callisto](https://blockscout.com/callisto/mainnet)
* [RSK](https://blockscout.com/rsk/mainnet)
#### Additional Chains Utilizing BlockScout
@ -59,6 +62,10 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
* [Fuse Network](https://explorer.fuse.io/)
* [ARTIS](https://explorer.sigma1.artis.network)
* [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/)
* [PIRL](http://pirl.es/)
* [Petrichor](https://explorer.petrichor-dev.com/)
### Visual Interface
@ -77,7 +84,7 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel
| Dependency | Mac | Linux |
|-------------|-----|-------|
| [Erlang/OTP 21.0.4](https://github.com/erlang/otp) | `brew install erlang` | [Erlang Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L134) |
| [Elixir 1.7.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
| [Elixir 1.8.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
| [Postgres 10.3](https://www.postgresql.org/) | `brew install postgresql` | [Postgres Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L187) |
| [Node.js 10.5.0](https://nodejs.org/en/) | `brew install node` | [Node.js Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L66) |
| [Automake](https://www.gnu.org/software/automake/) | `brew install automake` | [Automake Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L72) |
@ -116,7 +123,7 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel
`cd apps/explorer && npm install; cd -`
7. Update your JSON RPC Variant in `apps/explorer/config/dev.exs` and `apps/indexer/config/dev.exs`.
For `variant`, enter `ganache`, `geth`, or `parity`
For `variant`, enter `ganache`, `geth`, `parity`, or `rsk`
8. Update your JSON RPC Endpoint in `apps/explorer/config/dev/` and `apps/indexer/config/dev/`
For the `variant` chosen in step 7, enter the correct information for the corresponding JSON RPC Endpoint in `parity.exs`, `geth.exs`, or `ganache.exs`

@ -6,6 +6,22 @@ pre {
white-space: pre-wrap;
}
.pre-decompiled code {
white-space: pre-wrap;
counter-increment: line;
}
.pre-decompiled code::before {
content: counter(line);
display: inline-block;
width: flex;
border-right: 1px solid #ddd;
padding: 0 .5em;
margin-right: .5em;
color: #888;
-webkit-user-select: none;
}
.pre-scrollable-shorty {
max-height: $pre-scrollable-max-height / 7;
}

@ -31,6 +31,8 @@ import './pages/pending_transactions'
import './pages/transaction'
import './pages/transactions'
import './pages/admin/tasks.js'
import './lib/clipboard_buttons'
import './lib/currency'
import './lib/from_now'

@ -19,7 +19,15 @@ function tryUpdateAge (el) {
if (timestamp.isValid()) updateAge(el, timestamp)
}
function updateAge (el, timestamp) {
const fromNow = timestamp.fromNow()
let fromNow = timestamp.fromNow()
// show the exact time only for transaction details page. Otherwise, short entry
const elInTile = el.hasAttribute('in-tile')
if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/blocks/')) && !elInTile) {
const offset = moment().utcOffset() / 60
const sign = offset && Math.sign(offset) ? '+' : '-'
const formatDate = `MMMM-DD-YYYY hh:mm:ss A ${sign}${offset} UTC`
fromNow = `${fromNow} (${timestamp.format(formatDate)})`
}
if (fromNow !== el.innerHTML) el.innerHTML = fromNow
}
updateAllAges()

@ -0,0 +1,26 @@
import $ from 'jquery'
const runTask = (event) => {
const element = event.currentTarget
const $element = $(element)
const $loading = $element.find('[data-loading-message]')
const $errorMessage = $element.find('[data-error-message]')
const $successMessage = $element.find('[data-success-message]')
const apiPath = element.dataset.api_path
$errorMessage.hide()
$successMessage.hide()
$loading.show()
$.get(apiPath)
.done(response => {
$successMessage.show()
$loading.hide()
})
.fail(() => {
$loading.hide()
$errorMessage.show()
})
}
$('#run-create-contract-methods').click(runTask)

@ -9,7 +9,9 @@ use Mix.Config
config :block_scout_web,
namespace: BlockScoutWeb,
ecto_repos: [Explorer.Repo],
version: System.get_env("BLOCKSCOUT_VERSION")
version: System.get_env("BLOCKSCOUT_VERSION"),
release_link: System.get_env("RELEASE_LINK"),
decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN")
config :block_scout_web, BlockScoutWeb.Chain,
network: System.get_env("NETWORK"),
@ -66,6 +68,18 @@ config :block_scout_web,
%{
title: "Ethereum Classic",
url: "https://blockscout.com/etc/mainnet"
},
%{
title: "Aerum Mainnet",
url: "https://blockscout.com/aerum/mainnet"
},
%{
title: "Callisto Mainnet",
url: "https://blockscout.com/callisto/mainnet"
},
%{
title: "RSK Mainnet",
url: "https://blockscout.com/rsk/mainnet"
}
]
@ -91,7 +105,7 @@ config :block_scout_web, BlockScoutWeb.Gettext, locales: ~w(en), default_locale:
config :block_scout_web, BlockScoutWeb.SocialMedia,
twitter: "PoaNetwork",
telegram: "oraclesnetwork",
telegram: "poa_network",
facebook: "PoaNetwork",
instagram: "PoaNetwork"

@ -16,7 +16,7 @@ config :logger, :block_scout_web,
path: Path.absname("logs/test/block_scout_web.log")
# Configure wallaby
config :wallaby, screenshot_on_failure: true
config :wallaby, screenshot_on_failure: true, driver: Wallaby.Experimental.Chrome
config :explorer, Explorer.ExchangeRates, enabled: false, store: :none

@ -54,6 +54,8 @@ defmodule BlockScoutWeb do
Views.ScriptHelpers,
WeiHelpers
}
import PhoenixFormAwesomplete
end
end

@ -43,5 +43,9 @@ defmodule BlockScoutWeb.AdminRouter do
pipe_through([:browser, :check_configured, :ensure_admin])
get("/", DashboardController, :index)
scope "/tasks" do
get("/create_contract_methods", TaskController, :create_contract_methods, as: :create_contract_methods)
end
end
end

@ -11,7 +11,8 @@ defmodule BlockScoutWeb.Chain do
number_to_block: 1,
string_to_address_hash: 1,
string_to_block_hash: 1,
string_to_transaction_hash: 1
string_to_transaction_hash: 1,
token_contract_address_from_token_name: 1
]
alias Explorer.Chain.Block.Reward
@ -67,10 +68,10 @@ defmodule BlockScoutWeb.Chain do
end
end
def from_param(formatted_number) when is_binary(formatted_number) do
case param_to_block_number(formatted_number) do
def from_param(string) when is_binary(string) do
case param_to_block_number(string) do
{:ok, number} -> number_to_block(number)
{:error, :invalid} -> {:error, :not_found}
_ -> token_address_from_name(string)
end
end
@ -114,7 +115,7 @@ defmodule BlockScoutWeb.Chain do
end
end
def paging_options(%{"index" => index_string}) do
def paging_options(%{"index" => index_string}) when is_binary(index_string) do
with {index, ""} <- Integer.parse(index_string) do
[paging_options: %{@default_paging_options | key: {index}}]
else
@ -123,6 +124,10 @@ defmodule BlockScoutWeb.Chain do
end
end
def paging_options(%{"index" => index}) when is_integer(index) do
[paging_options: %{@default_paging_options | key: {index}}]
end
def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}) do
with {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string),
{:ok, hash} <- string_to_transaction_hash(hash_string) do
@ -159,6 +164,13 @@ defmodule BlockScoutWeb.Chain do
end
end
defp token_address_from_name(name) do
case token_contract_address_from_token_name(name) do
{:ok, hash} -> find_or_insert_address_from_hash(hash)
_ -> {:error, :not_found}
end
end
defp paging_params({%Reward{block: %{number: number}}, _}) do
%{"block_number" => number, "index" => 0}
end

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
alias BlockScoutWeb.AddressCoinBalanceView
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@ -62,7 +62,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
{:ok, address} <- Chain.hash_to_address(address_hash) do
render(conn, "index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address),

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressContractController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
@ -14,7 +14,7 @@ defmodule BlockScoutWeb.AddressContractController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address)

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{Publisher, Solidity.CompilerVersion}
alias Explorer.SmartContract.{Publisher, Solidity.CodeCompiler, Solidity.CompilerVersion}
def new(conn, %{"address_id" => address_hash_string}) do
changeset =
@ -13,7 +13,11 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions)
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
evm_versions: CodeCompiler.allowed_evm_versions()
)
end
def create(
@ -21,17 +25,35 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
%{
"address_id" => address_hash_string,
"smart_contract" => smart_contract,
"external_libraries" => external_libraries
"external_libraries" => external_libraries,
"evm_version" => evm_version,
"optimization" => optimization
}
) do
case Publisher.publish(address_hash_string, smart_contract, external_libraries) do
smart_sontact_with_evm_version =
smart_contract
|> Map.put("evm_version", evm_version["evm_version"])
|> Map.put("optimization_runs", parse_optimization_runs(optimization))
case Publisher.publish(address_hash_string, smart_sontact_with_evm_version, external_libraries) do
{:ok, _smart_contract} ->
redirect(conn, to: address_contract_path(conn, :index, address_hash_string))
{:error, changeset} ->
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions)
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
evm_versions: CodeCompiler.allowed_evm_versions()
)
end
end
def parse_optimization_runs(%{"runs" => runs}) do
case Integer.parse(runs) do
{integer, ""} -> integer
_ -> 200
end
end
end

@ -0,0 +1,30 @@
defmodule BlockScoutWeb.AddressDecompiledContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_decompiled_contract_address(address_hash) do
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
alias BlockScoutWeb.InternalTransactionView
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@ -67,7 +67,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
@ -21,7 +21,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address)

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.AddressTokenController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
@ -18,7 +18,7 @@ defmodule BlockScoutWeb.AddressTokenController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address),

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
alias BlockScoutWeb.TransactionView
alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
@ -81,7 +81,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: current_path(conn),
token: token,

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
alias BlockScoutWeb.TransactionView
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@transaction_necessity_by_association [
@ -91,7 +91,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
transaction_count: transaction_count(address),

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressValidationController do
alias BlockScoutWeb.BlockView
alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
alias Indexer.CoinBalance.OnDemandFetcher
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@ -76,7 +76,7 @@ defmodule BlockScoutWeb.AddressValidationController do
conn,
"index.html",
address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn),
transaction_count: transaction_count(address),
validation_count: validation_count(address),

@ -0,0 +1,22 @@
defmodule BlockScoutWeb.Admin.TaskController do
use BlockScoutWeb, :controller
require Logger
alias Explorer.Chain.ContractMethod
@ok_resp Poison.encode!(%{status: "success"})
@not_ok_resp Poison.encode!(%{status: "failure"})
def create_contract_methods(conn, _) do
case ContractMethod.import_all() do
:ok ->
send_resp(conn, 200, Poison.encode!(@ok_resp))
{:error, error} ->
Logger.error(fn -> ["Something went wrong while creating contract methods: ", inspect(error)] end)
send_resp(conn, 500, Poison.encode!(@not_ok_resp))
end
end
end

@ -1,9 +1,24 @@
defmodule BlockScoutWeb.API.RPC.AddressController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.{Chain, Etherscan}
alias Explorer.Chain.{Address, Wei}
def listaccounts(conn, params) do
options =
params
|> optional_params()
|> Map.put_new(:page_number, 0)
|> Map.put_new(:page_size, 10)
accounts = list_accounts(options)
conn
|> put_status(200)
|> render(:listaccounts, %{accounts: accounts})
end
def balance(conn, params, template \\ :balance) do
with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do
@ -148,7 +163,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end
def getminedblocks(conn, params) do
options = put_pagination_options(%{}, params)
options = Helpers.put_pagination_options(%{}, params)
with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param),
@ -174,7 +189,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
def optional_params(params) do
%{}
|> put_order_by_direction(params)
|> put_pagination_options(params)
|> Helpers.put_pagination_options(params)
|> put_start_block(params)
|> put_end_block(params)
|> put_filter_by(params)
@ -260,6 +275,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
Enum.any?(address_hashes, &(&1 == :error))
end
defp list_accounts(%{page_number: page_number, page_size: page_size}) do
offset = (max(page_number, 1) - 1) * page_size
# limit is just page_size
Chain.list_ordered_addresses(offset, page_size)
end
defp hashes_to_addresses(address_hashes) do
address_hashes
|> Chain.hashes_to_addresses()
@ -317,24 +339,6 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end
end
defp put_pagination_options(options, params) do
with %{"page" => page, "offset" => offset} <- params,
{page_number, ""} when page_number > 0 <- Integer.parse(page),
{page_size, ""} when page_size > 0 <- Integer.parse(offset),
:ok <- validate_max_page_size(page_size) do
options
|> Map.put(:page_number, page_number)
|> Map.put(:page_size, page_size)
else
_ ->
options
end
end
defp validate_max_page_size(page_size) do
if page_size <= Etherscan.page_size_max(), do: :ok, else: :error
end
defp put_start_block(options, params) do
with %{"startblock" => startblock_param} <- params,
{start_block, ""} <- Integer.parse(startblock_param) do

@ -1,7 +1,48 @@
defmodule BlockScoutWeb.API.RPC.ContractController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Publisher
def verify(conn, %{"addressHash" => address_hash} = params) do
with {:params, {:ok, fetched_params}} <- {:params, fetch_verify_params(params)},
{:params, external_libraries} <-
{:params, fetch_external_libraries(params)},
{:publish, {:ok, smart_contract}} <-
{:publish, Publisher.publish(address_hash, fetched_params, external_libraries)},
preloaded_smart_contract <- SmartContract.preload_decompiled_smart_contract(smart_contract) do
render(conn, :verify, %{contract: preloaded_smart_contract, address_hash: address_hash})
else
{:publish, _} ->
render(conn, :error, error: "Something went wrong while publishing the contract.")
{:params, {:error, error}} ->
render(conn, :error, error: error)
end
end
def listcontracts(conn, params) do
with pagination_options <- Helpers.put_pagination_options(%{}, params),
{:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do
options_with_defaults =
options
|> Map.put_new(:page_number, 0)
|> Map.put_new(:page_size, 10)
contracts = list_contracts(options_with_defaults)
conn
|> put_status(200)
|> render(:listcontracts, %{contracts: contracts})
else
{:params, {:error, error}} ->
conn
|> put_status(400)
|> render(:error, error: error)
end
end
def getabi(conn, params) do
with {:address_param, {:ok, address_param}} <- fetch_address(params),
@ -24,7 +65,10 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param),
{:contract, {:ok, contract}} <- to_smart_contract(address_hash) do
render(conn, :getsourcecode, %{contract: contract})
render(conn, :getsourcecode, %{
contract: contract,
address_hash: address_hash
})
else
{:address_param, :error} ->
render(conn, :error, error: "Query parameter address is required")
@ -33,10 +77,82 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
render(conn, :error, error: "Invalid address hash")
{:contract, :not_found} ->
render(conn, :getsourcecode, %{contract: nil})
render(conn, :getsourcecode, %{contract: nil, address_hash: nil})
end
end
defp list_contracts(%{page_number: page_number, page_size: page_size} = opts) do
offset = (max(page_number, 1) - 1) * page_size
case Map.get(opts, :filter) do
:verified ->
Chain.list_verified_contracts(page_size, offset)
:decompiled ->
not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version)
Chain.list_decompiled_contracts(page_size, offset, not_decompiled_with_version)
:unverified ->
Chain.list_unverified_contracts(page_size, offset)
:not_decompiled ->
Chain.list_not_decompiled_contracts(page_size, offset)
_ ->
Chain.list_contracts(page_size, offset)
end
end
defp add_filters(options, params) do
options
|> add_filter(params)
|> add_not_decompiled_with_version(params)
end
defp add_filter(options, params) do
with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")},
{:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do
{:ok, Map.put(options, :filter, filter)}
else
{:param, :error} -> {:ok, options}
{:validation, {:error, error}} -> {:error, error}
end
end
defp add_not_decompiled_with_version({:ok, options}, params) do
case Map.fetch(params, "not_decompiled_with_version") do
{:ok, value} -> {:ok, Map.put(options, :not_decompiled_with_version, value)}
:error -> {:ok, options}
end
end
defp add_not_decompiled_with_version(options, _params) do
options
end
defp contracts_filter(nil), do: {:ok, nil}
defp contracts_filter(1), do: {:ok, :verified}
defp contracts_filter(2), do: {:ok, :decompiled}
defp contracts_filter(3), do: {:ok, :unverified}
defp contracts_filter(4), do: {:ok, :not_decompiled}
defp contracts_filter("verified"), do: {:ok, :verified}
defp contracts_filter("decompiled"), do: {:ok, :decompiled}
defp contracts_filter("unverified"), do: {:ok, :unverified}
defp contracts_filter("not_decompiled"), do: {:ok, :not_decompiled}
defp contracts_filter(filter) when is_bitstring(filter) do
case Integer.parse(filter) do
{number, ""} -> contracts_filter(number)
_ -> {:error, contracts_filter_error_message(filter)}
end
end
defp contracts_filter(filter), do: {:error, contracts_filter_error_message(filter)}
defp contracts_filter_error_message(filter) do
"#{filter} is not a valid value for `filter`. Please use one of: verified, decompiled, unverified, not_decompiled, 1, 2, 3, 4."
end
defp fetch_address(params) do
{:address_param, Map.fetch(params, "address")}
end
@ -48,10 +164,80 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
defp to_smart_contract(address_hash) do
result =
case Chain.address_hash_to_smart_contract(address_hash) do
nil -> :not_found
contract -> {:ok, contract}
nil ->
:not_found
contract ->
{:ok, SmartContract.preload_decompiled_smart_contract(contract)}
end
{:contract, result}
end
defp fetch_verify_params(params) do
{:ok, %{}}
|> required_param(params, "addressHash", "address_hash")
|> required_param(params, "name", "name")
|> required_param(params, "compilerVersion", "compiler_version")
|> required_param(params, "optimization", "optimization")
|> required_param(params, "contractSourceCode", "contract_source_code")
|> optional_param(params, "evmVersion", "evm_version")
|> optional_param(params, "constructorArguments", "constructor_arguments")
|> optional_param(params, "optimizationRuns", "optimization_runs")
|> parse_optimization_runs()
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_integer(runs) do
{:ok, opts}
end
defp parse_optimization_runs({:ok, opts}) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs(other), do: other
defp fetch_external_libraries(params) do
Enum.reduce(1..5, %{}, fn number, acc ->
case Map.fetch(params, "library#{number}Name") do
{:ok, library_name} ->
library_address = Map.get(params, "library#{number}Address")
acc
|> Map.put("library#{number}_name", library_name)
|> Map.put("library#{number}_address", library_address)
:error ->
acc
end
end)
end
defp required_param({:error, _} = error, _, _, _), do: error
defp required_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:error, "#{key} is required."}
end
end
defp optional_param({:error, _} = error, _, _, _), do: error
defp optional_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:ok, map}
end
end
end

@ -0,0 +1,42 @@
defmodule BlockScoutWeb.API.RPC.Helpers do
@moduledoc """
Small helpers for RPC api controllers.
"""
alias Explorer.Etherscan
def put_pagination_options(options, params) do
options
|> put_page_option(params)
|> put_offset_option(params)
end
def put_page_option(options, %{"page" => page}) do
case Integer.parse(page) do
{page_number, ""} when page_number > 0 ->
Map.put(options, :page_number, page_number)
_ ->
options
end
end
def put_page_option(options, _), do: options
def put_offset_option(options, %{"offset" => offset}) do
with {page_size, ""} when page_size > 0 <- Integer.parse(offset),
:ok <- validate_max_page_size(page_size) do
Map.put(options, :page_size, page_size)
else
_ ->
options
end
end
def put_offset_option(options, _) do
options
end
defp validate_max_page_size(page_size) do
if page_size <= Etherscan.page_size_max(), do: :ok, else: :error
end
end

@ -13,6 +13,8 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
"""
require Logger
import Plug.Conn
import Phoenix.Controller, only: [put_view: 2]
@ -28,12 +30,28 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
{:ok, conn} <- call_controller(conn, controller, action) do
conn
else
_ ->
{:error, :no_action} ->
conn
|> put_status(400)
|> put_view(RPCView)
|> Controller.render(:error, error: "Unknown action")
|> halt()
{:error, error} ->
Logger.error(fn -> ["Error while calling RPC action", inspect(error)] end)
conn
|> put_status(500)
|> put_view(RPCView)
|> Controller.render(:error, error: "Something went wrong.")
|> halt()
_ ->
conn
|> put_status(500)
|> put_view(RPCView)
|> Controller.render(:error, error: "Something went wrong.")
|> halt()
end
end
@ -46,26 +64,35 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
end
@doc false
@spec translate_module(map(), String.t()) :: {:ok, module()} | :error
def translate_module(translations, module) do
@spec translate_module(map(), String.t()) :: {:ok, module()} | {:error, :no_action}
defp translate_module(translations, module) do
module_lowercase = String.downcase(module)
Map.fetch(translations, module_lowercase)
case Map.fetch(translations, module_lowercase) do
{:ok, module} -> {:ok, module}
_ -> {:error, :no_action}
end
end
@doc false
@spec translate_action(String.t()) :: {:ok, atom()} | :error
def translate_action(action) do
@spec translate_action(String.t()) :: {:ok, atom()} | {:error, :no_action}
defp translate_action(action) do
action_lowercase = String.downcase(action)
{:ok, String.to_existing_atom(action_lowercase)}
rescue
ArgumentError -> :error
ArgumentError -> {:error, :no_action}
end
@doc false
@spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | :error
def call_controller(conn, controller, action) do
{:ok, controller.call(conn, action)}
@spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | {:error, :no_action} | {:error, Exception.t()}
defp call_controller(conn, controller, action) do
if :erlang.function_exported(controller, action, 2) do
{:ok, controller.call(conn, action)}
else
{:error, :no_action}
end
rescue
Conn.WrapperError -> :error
e ->
{:error, Exception.format(:error, e, __STACKTRACE__)}
end
end

@ -1,14 +1,24 @@
defmodule BlockScoutWeb.API.RPC.TransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.Chain
def gettxinfo(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash) do
logs = Chain.transaction_to_logs(transaction)
render(conn, :gettxinfo, %{transaction: transaction, block_height: Chain.block_height(), logs: logs})
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction, paging_options)
{logs, next_page} = split_list_by_page(logs)
render(conn, :gettxinfo, %{
transaction: transaction,
block_height: Chain.block_height(),
logs: logs,
next_page_params: next_page_params(next_page, logs, params)
})
else
{:transaction, :error} ->
render(conn, :error, error: "Transaction not found")

@ -0,0 +1,79 @@
defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.Hash.Address
def create(conn, params) do
if auth_token(conn) == actual_token() do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash),
:ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do
case Chain.create_decompiled_smart_contract(params) do
{:ok, decompiled_smart_contract} ->
send_resp(conn, :created, Jason.encode!(decompiled_smart_contract))
{:error, changeset} ->
errors =
changeset.errors
|> Enum.into(%{}, fn {field, {message, _}} ->
{field, message}
end)
send_resp(conn, :unprocessable_entity, encode(errors))
end
else
:invalid_address ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"}))
:not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists ->
send_resp(
conn,
:unprocessable_entity,
encode(%{error: "decompiled code already exists for the decompiler version"})
)
end
else
send_resp(conn, :forbidden, "")
end
end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash}
:error -> :invalid_address
end
end
defp decompiled_contract_exists?(address_hash, decompiler_version) do
case Chain.decompiled_code(address_hash, decompiler_version) do
{:ok, _} -> :contract_exists
_ -> :ok
end
end
defp auth_token(conn) do
case get_req_header(conn, "auth_token") do
[token] -> token
other -> other
end
end
defp actual_token do
Application.get_env(:block_scout_web, :decompiled_smart_contract_token)
end
defp encode(data) do
Jason.encode!(data)
end
end

@ -39,6 +39,23 @@ defmodule BlockScoutWeb.ChainController do
end
end
def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do
if term == "" do
json(conn, "{}")
else
result =
term
|> String.trim()
|> Chain.search_token()
json(conn, result)
end
end
def token_autocomplete(conn, _) do
json(conn, "{}")
end
def chain_blocks(conn, _params) do
if ajax?(conn) do
blocks =

@ -13,8 +13,8 @@ defmodule BlockScoutWeb.CSPHeader do
"content-security-policy" => "\
connect-src 'self' #{websocket_endpoints(conn)}; \
default-src 'self';\
script-src 'self' 'unsafe-inline' 'unsafe-eval';\
style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://nico-amsterdam.github.io;\
style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://nico-amsterdam.github.io;\
img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\
font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\
"

@ -168,6 +168,17 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@account_listaccounts_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"address" => "0x0000000000000000000000000000000000000000",
"balance" => "135499"
}
]
}
@account_getminedblocks_example_value_error %{
"status" => "0",
"message" => "No blocks found",
@ -265,6 +276,45 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil
}
@contract_listcontracts_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"SourceCode" => """
pragma solidity >0.4.24;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
""",
"ABI" => """
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
""",
"ContractName" => "Test",
"CompilerVersion" => "v0.2.1-2016-01-30-91a6b35",
"OptimizationUsed" => "1"
}
]
}
@contract_getabi_example_value %{
"status" => "1",
"message" => "OK",
@ -278,6 +328,49 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil
}
@contract_verify_example_value %{
"status" => "1",
"message" => "OK",
"result" => %{
"SourceCode" => """
pragma solidity >0.4.24;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
""",
"ABI" => """
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
""",
"ContractName" => "Test",
"CompilerVersion" => "v0.2.1-2016-01-30-91a6b35",
"OptimizationUsed" => "1"
}
}
@contract_verify_example_value_error %{
"status" => "0",
"message" => "There was an error verifying the contract.",
"result" => nil
}
@contract_getsourcecode_example_value %{
"status" => "1",
"message" => "OK",
@ -720,9 +813,18 @@ defmodule BlockScoutWeb.Etherscan do
}
}
@account_model %{
name: "Account",
fields: %{
"address" => @address_hash_type,
"balance" => @wei_type
}
}
@contract_model %{
name: "Contract",
fields: %{
"Address" => @address_hash_type,
"SourceCode" => %{
type: "contract source code",
definition: "The contract's source code.",
@ -738,6 +840,33 @@ defmodule BlockScoutWeb.Etherscan do
}"
"""
},
"DecompilerVersion" => %{
type: "decompiler version",
definition: "When decompiled source code is present, the decompiler version with which it was generated.",
example: "decompiler.version"
},
"DecompiledSourceCode" => %{
type: "contract decompiled source code",
definition: "The contract's decompiled source code.",
example: """
const name() = 'CryptoKitties'
const GEN0_STARTING_PRICE() = 10^16
const GEN0_AUCTION_DURATION() = 86400
const GEN0_CREATION_LIMIT() = 45000
const symbol() = 'CK'
const PROMO_CREATION_LIMIT() = 5000
def storage:
ceoAddress is addr # mask(160, 0) at storage #0
cfoAddress is addr # mask(160, 0) at storage #1
stor1.768 is uint16 => uint256 # mask(256, 768) at storage #1
cooAddress is addr # mask(160, 0) at storage #2
stor2.0 is uint256 => uint256 # mask(256, 0) at storage #2
paused is uint8 # mask(8, 160) at storage #2
stor2.256 is uint256 => uint256 # mask(256, 256) at storage #2
stor3 is uint32 #
...<continues>
"""
},
"ABI" => %{
type: "ABI",
definition: "JSON string for the contract's Application Binary Interface (ABI)",
@ -1289,6 +1418,45 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@account_listaccounts_action %{
name: "listaccounts",
description:
"Get a list of accounts and their balances, sorted ascending by the time they were first seen by the explorer.",
required_params: [],
optional_params: [
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@account_listaccounts_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @account_model
}
}
}
}
]
}
@logs_getlogs_action %{
name: "getLogs",
description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.",
@ -1572,6 +1740,176 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@contract_listcontracts_action %{
name: "listcontracts",
description: "Get a list of contracts, sorted ascending by the time they were first seen by the explorer.",
required_params: [],
optional_params: [
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
},
%{
key: "filter",
type: "string",
description:
"verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status."
},
%{
key: "not_decompiled_with_version",
type: "string",
description:
"Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_listcontracts_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @contract_model
}
}
}
}
]
}
@contract_verify_action %{
name: "verify",
description: "Verify a contract with its source code and contract creation information.",
required_params: [
%{
key: "addressHash",
placeholder: "addressHash",
type: "string",
description: "The address of the contract."
},
%{
key: "name",
placeholder: "name",
type: "string",
description: "The name of the contract."
},
%{
key: "compilerVersion",
placeholder: "compilerVersion",
type: "string",
description: "The compiler version for the contract."
},
%{
key: "optimization",
placeholder: false,
type: "boolean",
description: "Whether or not compiler optimizations were enabled."
},
%{
key: "contractSourceCode",
placeholder: "contractSourceCode",
type: "string",
description: "The source code of the contract."
}
],
optional_params: [
%{
key: "constructorArguments",
type: "string",
description: "The constructor argument data provided."
},
%{
key: "evmVersion",
placeholder: "evmVersion",
type: "string",
description: "The EVM version for the contract."
},
%{
key: "optimizationRuns",
placeholder: "optimizationRuns",
type: "integer",
description: "The number of optimization runs used during compilation"
},
%{
key: "library1Name",
type: "string",
description: "The name of the first library used."
},
%{
key: "library1Address",
type: "string",
description: "The address of the first library used."
},
%{
key: "library2Name",
type: "string",
description: "The name of the second library used."
},
%{
key: "library2Address",
type: "string",
description: "The address of the second library used."
},
%{
key: "library3Name",
type: "string",
description: "The name of the third library used."
},
%{
key: "library3Address",
type: "string",
description: "The address of the third library used."
},
%{
key: "library4Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library4Address",
type: "string",
description: "The address of the fourth library used."
},
%{
key: "library5Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library5Address",
type: "string",
description: "The address of the fourth library used."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_verify_example_value),
type: "model",
model: @contract_model
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@contract_verify_example_value_error)
}
]
}
@contract_getabi_action %{
name: "getabi",
description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.",
@ -1657,7 +1995,13 @@ defmodule BlockScoutWeb.Etherscan do
description: "Transaction hash. Hash of contents of the transaction."
}
],
optional_params: [],
optional_params: [
%{
key: "index",
type: "integer",
description: "A nonnegative integer that represents the log index to be used for pagination."
}
],
responses: [
%{
code: "200",
@ -1767,7 +2111,8 @@ defmodule BlockScoutWeb.Etherscan do
@account_tokentx_action,
@account_tokenbalance_action,
@account_tokenlist_action,
@account_getminedblocks_action
@account_getminedblocks_action,
@account_listaccounts_action
]
}
@ -1798,8 +2143,10 @@ defmodule BlockScoutWeb.Etherscan do
@contract_module %{
name: "contract",
actions: [
@contract_listcontracts_action,
@contract_getabi_action,
@contract_getsourcecode_action
@contract_getsourcecode_action,
@contract_verify_action
]
}

@ -116,7 +116,7 @@ defmodule BlockScoutWeb.Notifier do
defp broadcast_block(block) do
preloaded_block = Repo.preload(block, [[miner: :names], :transactions, :rewards])
average_block_time = AverageBlockTime.average_block_time(preloaded_block)
average_block_time = AverageBlockTime.average_block_time()
Endpoint.broadcast("blocks:new_block", "new_block", %{
block: preloaded_block,

@ -22,6 +22,8 @@ defmodule BlockScoutWeb.Router do
pipe_through(:api)
get("/supply", SupplyController, :supply)
resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create])
end
scope "/api", BlockScoutWeb.API.RPC do
@ -127,6 +129,13 @@ defmodule BlockScoutWeb.Router do
as: :contract
)
resources(
"/decompiled_contracts",
AddressDecompiledContractController,
only: [:index],
as: :decompiled_contract
)
resources(
"/contract_verifications",
AddressContractVerificationController,
@ -211,6 +220,8 @@ defmodule BlockScoutWeb.Router do
get("/search", ChainController, :search)
get("/token_autocomplete", ChainController, :token_autocomplete)
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)
get("/api_docs", APIDocsController, :index)

@ -4,17 +4,13 @@
<h2 class="card-title text-white balance-card-title"><%= gettext "Balance" %></h2>
<div class="text-right">
<h3 class="text-white" data-test="address_balance"><%= balance(@address) %></h3>
<%= unless match?({:pending, _}, @coin_balance_status) do %>
<%= if !match?({:pending, _}, @coin_balance_status) && !empty_exchange_rate?(@exchange_rate) do %>
<span class="text-white text-faded">
<span class="current-balance-in-wei"
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>"
<% if !empty_exchange_rate?(@exchange_rate) do %>
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
<% end %>
</spanc>
<% if !empty_exchange_rate?(@exchange_rate) do %>
<small>(@ <span data-usd-unit-price="<%= @exchange_rate.usd_value %>"></span>/<%= gettext("Ether") %>)</small>
<% end %>
<br>
</span>
<% end %>

@ -59,6 +59,17 @@
</li>
<% end %>
<%= if has_decompiled_code?(@address) do %>
<li class="nav-item">
<%= link(
to: address_decompiled_contract_path(@conn, :index, @address.hash),
class: "nav-link #{tab_status("decompiled_contracts", @conn.request_path)}") do %>
<%= gettext("Decompiled code") %>
<i class="far fa-check-circle text-success"></i>
<% end %>
</li>
<% end %>
<%= if smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item">
<%= link(
@ -115,6 +126,15 @@
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<%= if has_decompiled_code?(@address) do %>
<%= link(
to: address_decompiled_contract_path(@conn, :index, @address.hash),
class: "dropdown-item #{tab_status("contracts", @conn.request_path)}") do %>
<%= gettext("Decompiled code") %>
<i class="far fa-check-circle text-success"></i>
<% end %>
<% end %>
<% end %>
<% end %>
<%= if smart_contract_with_read_only_functions?(@address) do %>

@ -35,10 +35,18 @@
<% end %>
<span>
<span class="address-detail-item">
<span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span>
<%= gettext("Transactions Sent") %>
<%= if contract?(@address) do %>
<%= gettext(">=") %>
<span data-selector="transaction-count">
<%= incoming_transaction_count(@address) %>
</span>
<%= gettext("Incoming Transactions") %>
<% else %>
<span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span>
<%= gettext("Transactions Sent") %>
<% end %>
</span>
<span class="address-detail-item">
<%= gettext("Last Balance Update: Block #") %><span data-selector="fetched-coin-balance-block-number"><%= @address.fetched_coin_balance_block_number %></span>
@ -53,21 +61,27 @@
<% end %>
</span>
</div>
<% from_address_hash = from_address_hash(@address) %>
<%= if contract?(@address) do %>
<span class="text-muted" data-test="address_contract_creator">
<%= gettext "Created by" %>
<%= link(
trimmed_hash(from_address_hash(@address)),
to: address_path(@conn, :show, from_address_hash(@address))
) %>
<%= if from_address_hash do %>
<%= gettext "Created by" %>
<%= link(
trimmed_hash(from_address_hash(@address)),
to: address_path(@conn, :show, from_address_hash(@address))
) %>
<%= gettext "at" %>
<%= gettext "at" %>
<%= link(
trimmed_hash(transaction_hash(@address)),
to: transaction_path(@conn, :show, transaction_hash(@address)),
"data-test": "transaction_hash_link"
) %>
<%= link(
trimmed_hash(transaction_hash(@address)),
to: transaction_path(@conn, :show, transaction_hash(@address)),
"data-test": "transaction_hash_link"
) %>
<% else %>
<p class="alert alert-danger" role="alert"><%= gettext("Error: Could not determine contract creator.") %></p>
<% end %>
</span>
<% end %>
</div>

@ -27,6 +27,11 @@
<%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger" %>
</div>
<div class="form-group">
<%= label :evm_version, :evm_version, gettext("EVM Version") %>
<%= select :evm_version, :evm_version, @evm_versions, class: "form-control", selected: "petersburg", "aria-describedby": "evm-version-help-block" %>
</div>
<div class="form-group mb-4">
<%= label f, "Optimization" %>
@ -43,6 +48,11 @@
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %>
</div>
<div class="form-group">
<%= label f, :name, gettext("Optimization runs") %>
<%= text_input :optimization, :runs, value: 200, class: "form-control", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %>
</div>
<div class="form-group mb-4">
<%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code below") %>
<%= textarea f, :contract_source_code, class: "form-control monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %>
@ -50,7 +60,7 @@
</div>
<div class="form-group mb-4">
<%= label f, :contructor_arguments, gettext("Enter contructor arguments if the contract had any") %>
<%= label f, :contructor_arguments, gettext("Enter constructor arguments if the contract had any") %>
<%= textarea f, :constructor_arguments, class: "form-control monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %>
<%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger", "data-test": "contract-constructor-arguments-error" %>
</div>

@ -0,0 +1 @@
<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %>

@ -0,0 +1,30 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<div class="card-header">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
</div>
<%= for contract <- sort_contracts_by_version(@address.decompiled_smart_contracts) do %>
<div class="card-body">
<h3><%= gettext "Decompiler version" %></h3>
<div class="tile tile-muted">
<pre class="pre-wrap"><code class="nohighlight"><%= contract.decompiler_version %></code></pre>
</div>
<br>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Decompiled contract code" %></h3>
<button type="button" class="button button-secondary button-sm" id="button" data-clipboard-text="<%= contract.decompiled_source_code %>" aria-label="copy decompiled contract code">
<%= gettext "Copy Decompiled Contract Code" %>
</button>
</div>
<div class="tile tile-muted">
<pre class="pre-decompiled pre-scrollable"><%= raw(highlight_decompiled_code(contract.decompiled_source_code)) %></pre>
</div>
</section>
</div>
<% end %>
</div>
</section>

@ -1 +1,36 @@
<div data-test="administrator_dashboard"></div>
<div data-test="administrator_dashboard">
<div class="container">
<div class="row">
<h2>Tasks</h2>
</div>
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<div class="card-title">
<h3>Create Contract Methods</h3>
</div>
<div class="card-text">
<p>
<%= gettext("For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches.") %>
</p>
</div>
<button id="run-create-contract-methods" data-api_path="<%= BlockScoutWeb.AdminRouter.Helpers.create_contract_methods_path(@conn, :create_contract_methods) %>" class="btn btn-primary">
<%= gettext("Run") %>
<span data-loading-message class="loading-spinner-small mr-2" style="display: none;">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<span data-success-message style="display: none;"> - Success</span>
<span data-error-message style="display: none;"> - Failed</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>

@ -23,7 +23,7 @@
to: block_path(BlockScoutWeb.Endpoint, :show, @internal_transaction.block_number)
) %>
</span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= @internal_transaction.transaction.block.timestamp %>"></span>
<span class="mr-2 mr-md-0 order-2" in-tile data-from-now="<%= @internal_transaction.transaction.block.timestamp %>"></span>
<%= if assigns[:current_address] do %>
<span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %>

@ -11,13 +11,13 @@
<div class="col-md-3">
<div class="icon-links icon-links-primary footer-social-icons">
<a href="https://github.com/poanetwork" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Github") %>">
<a href="https://github.com/poanetwork/blockscout" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Github") %>">
<i class="fab fa-github"></i>
</a>
<a href="https://www.twitter.com/PoaNetwork/" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Twitter") %>">
<a href="https://www.twitter.com/_blockscout/" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Twitter") %>">
<i class="fab fa-twitter"></i>
</a>
<a href="http://t.me/oraclesnetwork" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Telegram") %>">
<a href="https://t.me/poa_network" rel="noreferrer" target="_blank" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("Telegram") %>">
<i class="fab fa-telegram-plane"></i>
</a>
</div>
@ -90,8 +90,9 @@
<% version = version() %>
<%= unless ignore_version?(version) do %>
<% release_link = release_link(version) %>
<div>
<%= gettext("Version") %>: <%= version %>
<%= gettext("Version") %>: <%= release_link %>
</div>
<% end %>
</div>

@ -79,13 +79,29 @@
</ul>
<div class="search-form d-lg-flex d-inline-block">
<%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %>
<div class="input-group">
<%= search_input f, :q, class: 'form-control mr-auto', placeholder: gettext("Search by address, transaction hash, or block number"), "aria-describedby": "search-icon", "aria-label": gettext("Search"), "data-test": "search_input" %>
<div class="input-group-append">
<button class="input-group-text" id="search-icon">
<%= render BlockScoutWeb.IconsView, "_search_icon.html" %>
</button>
</div>
<div class="input-group">
<%= awesomplete(f, :q,
[
class: "form-control me auto",
placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"),
"aria-describedby": "search-icon",
"aria-label": gettext("Search"),
"data-test": "search_input"
],
[ url: "#{chain_path(@conn, :token_autocomplete)}?q=",
prepop: true,
minChars: 3,
maxItems: 8,
value: "symbol",
label: "contract_address_hash",
descr: "symbol"
]) %>
<div class="input-group-append">
<button class="input-group-text" id="search-icon">
<%= render BlockScoutWeb.IconsView, "_search_icon.html" %>
</button>
</div>
</div>
<button class="btn btn-outline-success my-2 my-sm-0 sr-only" type="submit"><%= gettext "Search" %></button>
<% end %>

@ -16,6 +16,21 @@
<meta name="msapplication-config" content="<%= static_path(@conn, "/browserconfig.xml") %>">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="https://nico-amsterdam.github.io/awesomplete-util/css/awesomplete.css">
<script src="https://nico-amsterdam.github.io/awesomplete-util/js/awesomplete.min.js"></script>
<script src="https://nico-amsterdam.github.io/awesomplete-util/js/awesomplete-util.min.js"></script>
<style>
div.awesomplete {display: block}
div.awesomplete ul li p {display: block; font-size: small; margin-left: 1em}
div.awesomplete .awe-found {border: 2px solid green}
.hide-not-found div.awesomplete .awe-not-found {border-color: lightblue}
div.awesomplete .awe-not-found {border: 2px solid red}
</style>
<%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %>
</head>

@ -35,7 +35,7 @@
<span class="mr-2 mr-md-0 order-1">
<%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %>
</span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= block_timestamp(@transaction) %>"></span>
<span class="mr-2 mr-md-0 order-2" in-tile data-from-now="<%= block_timestamp(@transaction) %>"></span>
<%= if from_or_to_address?(@transaction, assigns[:current_address]) do %>
<span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if @transaction.from_address_hash == assigns[:current_address].hash do %>

@ -73,11 +73,10 @@
<dt class="col-sm-3 text-muted"> <%= gettext "TX Fee" %> </dt>
<dd class="col-sm-9">
<%= formatted_fee(@transaction, denomination: :ether) %>
(<span data-wei-value=<%= fee(@transaction) %>
<% if !empty_exchange_rate?(@exchange_rate) do %>
data-usd-exchange-rate=<%= @exchange_rate.usd_value %>
<% end %>
></span>)
<%= if !empty_exchange_rate?(@exchange_rate) do %>
(<span data-wei-value=<%= fee(@transaction) %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>)
<% end %>
</dd>
</dl>
<!-- Processing Time -->
@ -139,20 +138,56 @@
</div>
</div>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value -->
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2>
<div class="text-right">
<h3 class="text-white">
<%= value(@transaction) %>
</h3>
<span class="text-white text-faded" data-wei-value=<%= @transaction.value.value %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>
<%= cond do %>
<% erc20_token_transfer = assigns[:token_transfers] && erc20_token_transfer(@transaction, @token_transfers) -> %>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value -->
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "ERC-20" %> <%= gettext "Token Transfer" %></h2>
<div class="text-right">
<h3 class="text-white">
<span class="col-12 col-md-7 ml-3 ml-sm-0">
<%= token_transfer_amount(erc20_token_transfer) %>
<%= link(token_symbol(erc20_token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, erc20_token_transfer.token.contract_address_hash)) %>
</span>
</h3>
</div>
</div>
</div>
</div>
<% erc721_token_transfer = assigns[:token_transfers] && erc721_token_transfer(@transaction, @token_transfers) -> %>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value -->
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "ERC-721" %> <%= gettext "Token Transfer" %></h2>
<div class="text-right">
<h3 class="text-white">
<span class="col-12 col-md-7 ml-3 ml-sm-0">
<%= token_transfer_amount(erc721_token_transfer) %>
<%= link(token_symbol(erc721_token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, erc721_token_transfer.token.contract_address_hash)) %>
</span>
</h3>
</div>
</div>
</div>
<% true -> %>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value -->
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2>
<div class="text-right">
<h3 class="text-white">
<%= value(@transaction) %>
</h3>
<span class="text-white text-faded" data-wei-value=<%= @transaction.value.value %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>
</div>
</div>
</div>
<% end %>
<!-- Gas -->
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0">
<div class="card-body">

@ -0,0 +1,44 @@
defmodule BlockScoutWeb.AddressDecompiledContractView do
use BlockScoutWeb, :view
@colors %{
"\e[95m" => "136, 0, 0",
# red
"\e[91m" => "236, 89, 58",
# gray
"\e[38;5;8m" => "111, 110, 111",
# green
"\e[32m" => "57, 115, 0",
# yellowgreen
"\e[93m" => "57, 115, 0",
# yellow
"\e[92m" => "119, 232, 81",
# red
"\e[94m" => "136, 0, 0"
}
def highlight_decompiled_code(code) do
@colors
|> Enum.reduce(code, fn {symbol, rgb}, acc ->
String.replace(acc, symbol, "<span style=\"color:rgb(#{rgb})\">")
end)
|> String.replace("\e[1m", "<span style=\"font-weight:bold\">")
|> String.replace("»", "&raquo;")
|> String.replace("\e[0m", "</span>")
|> add_line_numbers()
end
def sort_contracts_by_version(decompiled_contracts) do
decompiled_contracts
|> Enum.sort_by(& &1.decompiler_version)
|> Enum.reverse()
end
defp add_line_numbers(code) do
code
|> String.split("\n")
|> Enum.reduce("", fn line, acc ->
acc <> "<code>#{line}</code>\n"
end)
end
end

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.AddressView do
use BlockScoutWeb, :view
require Logger
import BlockScoutWeb.AddressController, only: [validation_count: 1]
alias BlockScoutWeb.LayoutView
@ -14,6 +16,7 @@ defmodule BlockScoutWeb.AddressView do
@tabs [
"coin_balances",
"contracts",
"decompiled_contracts",
"internal_transactions",
"read_contract",
"tokens",
@ -203,6 +206,11 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def has_decompiled_code?(address) do
address.has_decompiled_code? ||
(Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0)
end
def token_title(%Token{name: nil, contract_address_hash: contract_address_hash}) do
contract_address_hash
|> to_string
@ -211,6 +219,12 @@ defmodule BlockScoutWeb.AddressView do
def token_title(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})"
def incoming_transaction_count(%Address{} = address) do
count = Chain.address_to_incoming_transaction_count(address)
Cldr.Number.to_string!(count, format: "#,###")
end
def trimmed_hash(%Hash{} = hash) do
string_hash = to_string(hash)
"#{String.slice(string_hash, 0..5)}#{String.slice(string_hash, -6..-1)}"
@ -234,6 +248,12 @@ defmodule BlockScoutWeb.AddressView do
address.contracts_creation_transaction.from_address_hash
end
def from_address_hash(address) do
Logger.error(fn -> ["Found a contract with no creator: ", to_string(address)] end)
nil
end
defp matching_address_check(%Address{hash: hash} = current_address, %Address{hash: hash}, contract?, truncate) do
[
view_module: __MODULE__,
@ -274,6 +294,7 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["transactions"]), do: gettext("Transactions")
defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions")
defp tab_name(["contracts"]), do: gettext("Code")
defp tab_name(["decompiled_contracts"]), do: gettext("Decompiled Code")
defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated")

@ -3,8 +3,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
alias BlockScoutWeb.API.RPC.RPCView
def render("listaccounts.json", %{accounts: accounts}) do
accounts = Enum.map(accounts, &prepare_account/1)
RPCView.render("show.json", data: accounts)
end
def render("balance.json", %{addresses: [address]}) do
RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}")
RPCView.render("show.json", data: balance(address))
end
def render("balance.json", assigns) do
@ -16,7 +21,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
Enum.map(addresses, fn address ->
%{
"account" => "#{address.hash}",
"balance" => "#{address.fetched_coin_balance.value}"
"balance" => balance(address)
}
end)
@ -56,6 +61,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("error.json", assigns)
end
defp prepare_account(address) do
%{
"balance" => to_string(address.fetched_coin_balance.value),
"address" => to_string(address.hash)
}
end
defp prepare_transaction(transaction) do
%{
"blockNumber" => "#{transaction.block_number}",
@ -145,4 +157,8 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"symbol" => token.symbol
}
end
defp balance(address) do
address.fetched_coin_balance && address.fetched_coin_balance.value && "#{address.fetched_coin_balance.value}"
end
end

@ -2,40 +2,108 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView
alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
def render("listcontracts.json", %{contracts: contracts}) do
contracts = Enum.map(contracts, &prepare_contract/1)
RPCView.render("show.json", data: contracts)
end
def render("getabi.json", %{abi: abi}) do
RPCView.render("show.json", data: Jason.encode!(abi))
end
def render("getsourcecode.json", %{contract: contract}) do
RPCView.render("show.json", data: prepare_contract(contract))
def render("getsourcecode.json", %{contract: contract, address_hash: address_hash}) do
RPCView.render("show.json", data: [prepare_source_code_contract(contract, address_hash)])
end
def render("error.json", assigns) do
RPCView.render("error.json", assigns)
end
defp prepare_contract(nil) do
[
%{
"SourceCode" => "",
"ABI" => "Contract source code not verified",
"ContractName" => "",
"CompilerVersion" => "",
"OptimizationUsed" => ""
}
]
end
defp prepare_contract(contract) do
[
%{
"SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
]
def render("verify.json", %{contract: contract, address_hash: address_hash}) do
RPCView.render("show.json", data: prepare_source_code_contract(contract, address_hash))
end
defp prepare_source_code_contract(nil, address_hash) do
%{
"Address" => to_string(address_hash),
"SourceCode" => "",
"ABI" => "Contract source code not verified",
"ContractName" => "",
"CompilerVersion" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => "",
"OptimizationUsed" => ""
}
end
defp prepare_source_code_contract(contract, _) do
decompiled_smart_contract = latest_decompiled_smart_contract(contract.decompiled_smart_contracts)
%{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
end
defp prepare_contract(%Address{
hash: hash,
smart_contract: nil,
decompiled_smart_contracts: decompiled_smart_contracts
}) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)
%{
"Address" => to_string(hash),
"SourceCode" => "",
"ABI" => "Contract source code not verified",
"ContractName" => "",
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => "",
"OptimizationUsed" => ""
}
end
defp prepare_contract(%Address{
hash: hash,
smart_contract: %SmartContract{} = contract,
decompiled_smart_contracts: decompiled_smart_contracts
}) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)
%{
"Address" => to_string(hash),
"SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
}
end
defp latest_decompiled_smart_contract([]), do: nil
defp latest_decompiled_smart_contract(contracts) do
Enum.max_by(contracts, fn contract -> DateTime.to_unix(contract.inserted_at) end)
end
defp decompiled_source_code(nil), do: "Contract source code not decompiled."
defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do
decompiled_source_code
end
defp decompiler_version(nil), do: ""
defp decompiler_version(%DecompiledSmartContract{decompiler_version: decompiler_version}), do: decompiler_version
end

@ -3,8 +3,13 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
alias BlockScoutWeb.API.RPC.RPCView
def render("gettxinfo.json", %{transaction: transaction, block_height: block_height, logs: logs}) do
data = prepare_transaction(transaction, block_height, logs)
def render("gettxinfo.json", %{
transaction: transaction,
block_height: block_height,
logs: logs,
next_page_params: next_page_params
}) do
data = prepare_transaction(transaction, block_height, logs, next_page_params)
RPCView.render("show.json", data: data)
end
@ -50,7 +55,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
}
end
defp prepare_transaction(transaction, block_height, logs) do
defp prepare_transaction(transaction, block_height, logs, next_page_params) do
%{
"hash" => "#{transaction.hash}",
"timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}",
@ -63,7 +68,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
"input" => "#{transaction.input}",
"gasLimit" => "#{transaction.gas}",
"gasUsed" => "#{transaction.gas_used}",
"logs" => Enum.map(logs, &prepare_log/1)
"logs" => Enum.map(logs, &prepare_log/1),
"next_page_params" => next_page_params
}
end

@ -76,6 +76,16 @@ defmodule BlockScoutWeb.LayoutView do
BlockScoutWeb.version()
end
def release_link(version) do
release_link = Application.get_env(:block_scout_web, :release_link)
if release_link == "" || release_link == nil do
version
else
html_escape({:safe, "<a href=\"#{release_link}\" class=\"footer-link\" target=\"_blank\">#{version}</a>"})
end
end
def ignore_version?("unknown"), do: true
def ignore_version?(_), do: false

@ -1,15 +1,17 @@
defmodule BlockScoutWeb.TransactionView do
use BlockScoutWeb, :view
alias ABI.TypeDecoder
alias BlockScoutWeb.{AddressView, BlockView, TabHelpers}
alias Cldr.Number
alias Explorer.Chain
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei}
alias Explorer.Chain.{Address, Block, InternalTransaction, TokenTransfer, Transaction, Wei}
alias Explorer.ExchangeRates.Token
alias Timex.Duration
import BlockScoutWeb.Gettext
import BlockScoutWeb.Tokens.Helpers
@tabs ["token_transfers", "internal_transactions", "logs"]
@ -31,6 +33,85 @@ defmodule BlockScoutWeb.TransactionView do
def value_transfer?(_), do: false
def erc20_token_transfer(
%Transaction{
status: :ok,
created_contract_address_hash: nil,
input: input,
value: value
},
token_transfers
) do
zero_wei = %Wei{value: Decimal.new(0)}
case {to_string(input), value} do
{unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} ->
types = [:address, {:uint, 256}]
[address, value] = decode_params(params, types)
decimal_value = Decimal.new(value)
Enum.find(token_transfers, fn token_transfer ->
token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value
end)
_ ->
nil
end
rescue
_ -> nil
end
def erc20_token_transfer(_, _) do
nil
end
def erc721_token_transfer(
%Transaction{
status: :ok,
created_contract_address_hash: nil,
input: input,
value: value
},
token_transfers
) do
zero_wei = %Wei{value: Decimal.new(0)}
# https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35
{from_address, to_address} =
case {to_string(input), value} do
# transferFrom(address,address,uint256)
{"0x23b872dd" <> params, ^zero_wei} ->
types = [:address, :address, {:uint, 256}]
[from_address, to_address, _value] = decode_params(params, types)
{from_address, to_address}
# safeTransferFrom(address,address,uint256)
{"0x42842e0e" <> params, ^zero_wei} ->
types = [:address, :address, {:uint, 256}]
[from_address, to_address, _value] = decode_params(params, types)
{from_address, to_address}
# safeTransferFrom(address,address,uint256,bytes)
{"0xb88d4fde" <> params, ^zero_wei} ->
types = [:address, :address, {:uint, 256}, :bytes]
[from_address, to_address, _value, _data] = decode_params(params, types)
{from_address, to_address}
_ ->
nil
end
Enum.find(token_transfers, fn token_transfer ->
token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address
end)
rescue
_ -> nil
end
def erc721_token_transfer(_, _), do: nil
def processing_time_duration(%Transaction{block: nil}) do
:pending
end
@ -257,4 +338,10 @@ defmodule BlockScoutWeb.TransactionView do
defp tab_name(["token_transfers"]), do: gettext("Token Transfers")
defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions")
defp tab_name(["logs"]), do: gettext("Logs")
defp decode_params(params, types) do
params
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)
end
end

@ -15,7 +15,7 @@ defmodule BlockScoutWeb.Mixfile do
plt_add_deps: :transitive,
ignore_warnings: "../../.dialyzer-ignore"
],
elixir: "~> 1.6",
elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
package: package(),
@ -122,13 +122,14 @@ defmodule BlockScoutWeb.Mixfile do
# Tracing
{:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true},
# `:spandex` integration with Datadog
{:spandex_datadog, "~> 0.3.1"},
{:spandex_datadog, "~> 0.4.0"},
# `:spandex` tracing of `:phoenix`
{:spandex_phoenix, "~> 0.3.1"},
{:timex, "~> 3.4"},
{:wallaby, "~> 0.20", only: [:test], runtime: false},
{:wallaby, "~> 0.22", only: [:test], runtime: false},
# `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility
{:wobserver, "~> 0.2.0", github: "KronicDeth/wobserver", ref: "99683a936c75c0a94ebb884cef019f7ed0b97112"}
{:wobserver, "~> 0.2.0", github: "KronicDeth/wobserver", ref: "99683a936c75c0a94ebb884cef019f7ed0b97112"},
{:phoenix_form_awesomplete, "~> 0.1.4"}
]
end

@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:128
#: lib/block_scout_web/views/transaction_view.ex:209
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -62,7 +62,7 @@ msgid "(query)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:31
#: lib/block_scout_web/templates/layout/app.html.eex:46
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@ -99,7 +99,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/index.html.eex:15
#: lib/block_scout_web/views/address_view.ex:98
#: lib/block_scout_web/views/address_view.ex:101
msgid "Address"
msgstr ""
@ -155,7 +155,7 @@ msgid "Block Height: %{height}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:46
#: lib/block_scout_web/templates/layout/app.html.eex:61
msgid "Block Mined, awaiting import..."
msgstr ""
@ -165,7 +165,7 @@ msgid "Block Number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:20
#: lib/block_scout_web/views/transaction_view.ex:22
msgid "Block Pending"
msgstr ""
@ -182,22 +182,22 @@ msgid "Blocks"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:45
#: lib/block_scout_web/templates/layout/app.html.eex:60
msgid "Blocks Indexed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:51
#: lib/block_scout_web/templates/address/_tabs.html.eex:114
#: lib/block_scout_web/templates/address/overview.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57
#: lib/block_scout_web/views/address_view.ex:279
#: lib/block_scout_web/views/address_view.ex:300
msgid "Blocks Validated"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel"
msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:89
#: lib/block_scout_web/templates/address/overview.html.eex:97
#: lib/block_scout_web/templates/address/overview.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:111
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close"
@ -218,11 +218,11 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53
#: lib/block_scout_web/templates/address/_tabs.html.eex:113
#: lib/block_scout_web/templates/address/_tabs.html.eex:124
#: lib/block_scout_web/templates/address_validation/index.html.eex:39
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:276
#: lib/block_scout_web/views/address_view.ex:296
msgid "Code"
msgstr ""
@ -266,24 +266,24 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:13
#: lib/block_scout_web/views/address_view.ex:96
#: lib/block_scout_web/views/address_view.ex:99
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:36
#: lib/block_scout_web/views/address_view.ex:70
#: lib/block_scout_web/views/address_view.ex:39
#: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:205
#: lib/block_scout_web/views/transaction_view.ex:286
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:204
#: lib/block_scout_web/views/transaction_view.ex:285
msgid "Contract Creation"
msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:58
#: lib/block_scout_web/templates/address/overview.html.eex:69
msgid "Created by"
msgstr ""
@ -367,32 +367,32 @@ msgid "ETH"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:47
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "Enter the Solidity Contract Code below"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:32
#: lib/block_scout_web/templates/address/_balance_card.html.eex:28
msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:132
#: lib/block_scout_web/views/transaction_view.ex:213
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:130
#: lib/block_scout_web/views/transaction_view.ex:211
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:16
#: lib/block_scout_web/templates/address/_balance_card.html.eex:13
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:16
#: lib/block_scout_web/templates/layout/app.html.eex:51
#: lib/block_scout_web/templates/layout/app.html.eex:66
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:146
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
msgstr ""
@ -408,7 +408,7 @@ msgid "Execute"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:28
#: lib/block_scout_web/templates/address/_balance_card.html.eex:24
msgid "Fetching tokens..."
msgstr ""
@ -431,7 +431,7 @@ msgid "GET"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:159
#: lib/block_scout_web/templates/transaction/overview.html.eex:194
msgid "Gas"
msgstr ""
@ -472,7 +472,7 @@ msgid "IN"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:47
#: lib/block_scout_web/templates/layout/app.html.eex:62
msgid "Indexing Tokens"
msgstr ""
@ -488,14 +488,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21
#: lib/block_scout_web/templates/address/_tabs.html.eex:90
#: lib/block_scout_web/templates/address/_tabs.html.eex:101
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:275
#: lib/block_scout_web/views/transaction_view.ex:258
#: lib/block_scout_web/views/address_view.ex:295
#: lib/block_scout_web/views/transaction_view.ex:339
msgid "Internal Transactions"
msgstr ""
@ -508,12 +508,12 @@ msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:48
#: lib/block_scout_web/templates/layout/app.html.eex:63
msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:171
#: lib/block_scout_web/templates/transaction/overview.html.eex:206
msgid "Limit"
msgstr ""
@ -521,19 +521,19 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:21
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:48
#: lib/block_scout_web/templates/transaction_log/index.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:259
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28
#: lib/block_scout_web/templates/layout/app.html.eex:49
#: lib/block_scout_web/views/address_view.ex:118
#: lib/block_scout_web/templates/layout/app.html.eex:64
#: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:113
#: lib/block_scout_web/views/transaction_view.ex:194
msgid "Max of"
msgstr ""
@ -603,7 +603,7 @@ msgid "Next Page"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
msgid "No"
msgstr ""
@ -661,8 +661,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
#: lib/block_scout_web/views/transaction_view.ex:127
#: lib/block_scout_web/views/transaction_view.ex:161
#: lib/block_scout_web/views/transaction_view.ex:208
#: lib/block_scout_web/views/transaction_view.ex:242
msgid "Pending"
msgstr ""
@ -679,13 +679,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:21
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/layout/app.html.eex:65
msgid "Price"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:88
#: lib/block_scout_web/templates/address/overview.html.eex:102
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -698,11 +698,11 @@ msgid "Query"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:65
#: lib/block_scout_web/templates/address/_tabs.html.eex:122
#: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/templates/address/_tabs.html.eex:142
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:277
#: lib/block_scout_web/views/address_view.ex:298
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
msgid "Reset"
msgstr ""
@ -728,16 +728,11 @@ msgid "Responses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
#: lib/block_scout_web/templates/layout/_topnav.html.eex:106
msgid "Search"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, transaction hash, or block number"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -767,7 +762,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:129
#: lib/block_scout_web/views/transaction_view.ex:210
msgid "Success"
msgstr ""
@ -877,8 +872,10 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction/overview.html.eex:148
#: lib/block_scout_web/templates/transaction/overview.html.eex:165
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:203
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Token Transfer"
msgstr ""
@ -890,18 +887,18 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:257
#: lib/block_scout_web/views/transaction_view.ex:338
msgid "Token Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:13
#: lib/block_scout_web/templates/address/_tabs.html.eex:85
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/address_token/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19
#: lib/block_scout_web/views/address_view.ex:273
#: lib/block_scout_web/views/address_view.ex:293
msgid "Tokens"
msgstr ""
@ -931,7 +928,7 @@ msgid "Total transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:206
#: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction"
msgstr ""
@ -953,7 +950,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:5
#: lib/block_scout_web/templates/address/_tabs.html.eex:80
#: lib/block_scout_web/templates/address/_tabs.html.eex:91
#: lib/block_scout_web/templates/address_transaction/index.html.eex:53
#: lib/block_scout_web/templates/address_validation/index.html.eex:14
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13
@ -962,7 +959,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:274
#: lib/block_scout_web/views/address_view.ex:294
msgid "Transactions"
msgstr ""
@ -998,7 +995,7 @@ msgid "Unique Token"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:164
#: lib/block_scout_web/templates/transaction/overview.html.eex:199
msgid "Used"
msgstr ""
@ -1019,7 +1016,7 @@ msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:146
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
msgid "Value"
msgstr ""
@ -1029,7 +1026,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:122
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132
msgid "Verify & publish"
msgstr ""
@ -1089,12 +1086,12 @@ msgid "Wei"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:45
msgid "Yes"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:64
#: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at"
msgstr ""
@ -1170,7 +1167,7 @@ msgid "Loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130
msgid "Loading...."
msgstr ""
@ -1215,7 +1212,7 @@ msgid "This API is provided for developers transitioning their applications from
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:100
#: lib/block_scout_web/templates/transaction/overview.html.eex:99
msgid "Raw Input"
msgstr ""
@ -1404,8 +1401,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/views/address_view.ex:278
#: lib/block_scout_web/templates/address/_tabs.html.eex:107
#: lib/block_scout_web/views/address_view.ex:299
msgid "Coin Balance History"
msgstr ""
@ -1557,7 +1554,7 @@ msgid "Test Networks"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_footer.html.eex:94
#: lib/block_scout_web/templates/layout/_footer.html.eex:95
msgid "Version"
msgstr ""
@ -1587,87 +1584,163 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "2 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111
msgid "5 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68
msgid "Contract Libraries"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53
msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
#: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Last Balance Update: Block #"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:41
#: lib/block_scout_web/templates/address/overview.html.eex:48
msgid "Transactions Sent"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/overview.html.eex:90
msgid "Transaction Speed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122
#: lib/block_scout_web/templates/transaction/overview.html.eex:127
#: lib/block_scout_web/templates/transaction/overview.html.eex:121
#: lib/block_scout_web/templates/transaction/overview.html.eex:126
msgid "Hex (Default)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:131
#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16
msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21
msgid "Run"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:43
msgid "Incoming Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:83
msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:87
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Version"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63
msgid "Enter constructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20
msgid "Copy Decompiled Contract Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:297
msgid "Decompiled Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:134
msgid "Decompiled code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18
msgid "Decompiled contract code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11
msgid "Decompiler version"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Optimization runs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:148
msgid "ERC-20"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:165
msgid "ERC-721"
msgstr ""

@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:128
#: lib/block_scout_web/views/transaction_view.ex:209
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -62,7 +62,7 @@ msgid "(query)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:31
#: lib/block_scout_web/templates/layout/app.html.eex:46
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@ -99,7 +99,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/index.html.eex:15
#: lib/block_scout_web/views/address_view.ex:98
#: lib/block_scout_web/views/address_view.ex:101
msgid "Address"
msgstr ""
@ -155,7 +155,7 @@ msgid "Block Height: %{height}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:46
#: lib/block_scout_web/templates/layout/app.html.eex:61
msgid "Block Mined, awaiting import..."
msgstr ""
@ -165,7 +165,7 @@ msgid "Block Number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:20
#: lib/block_scout_web/views/transaction_view.ex:22
msgid "Block Pending"
msgstr ""
@ -182,22 +182,22 @@ msgid "Blocks"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:45
#: lib/block_scout_web/templates/layout/app.html.eex:60
msgid "Blocks Indexed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:51
#: lib/block_scout_web/templates/address/_tabs.html.eex:114
#: lib/block_scout_web/templates/address/overview.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57
#: lib/block_scout_web/views/address_view.ex:279
#: lib/block_scout_web/views/address_view.ex:300
msgid "Blocks Validated"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel"
msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:89
#: lib/block_scout_web/templates/address/overview.html.eex:97
#: lib/block_scout_web/templates/address/overview.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:111
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close"
@ -218,11 +218,11 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53
#: lib/block_scout_web/templates/address/_tabs.html.eex:113
#: lib/block_scout_web/templates/address/_tabs.html.eex:124
#: lib/block_scout_web/templates/address_validation/index.html.eex:39
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:276
#: lib/block_scout_web/views/address_view.ex:296
msgid "Code"
msgstr ""
@ -266,24 +266,24 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:13
#: lib/block_scout_web/views/address_view.ex:96
#: lib/block_scout_web/views/address_view.ex:99
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:36
#: lib/block_scout_web/views/address_view.ex:70
#: lib/block_scout_web/views/address_view.ex:39
#: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:205
#: lib/block_scout_web/views/transaction_view.ex:286
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:204
#: lib/block_scout_web/views/transaction_view.ex:285
msgid "Contract Creation"
msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:58
#: lib/block_scout_web/templates/address/overview.html.eex:69
msgid "Created by"
msgstr ""
@ -367,32 +367,32 @@ msgid "ETH"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:47
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "Enter the Solidity Contract Code below"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:32
#: lib/block_scout_web/templates/address/_balance_card.html.eex:28
msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:132
#: lib/block_scout_web/views/transaction_view.ex:213
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:130
#: lib/block_scout_web/views/transaction_view.ex:211
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:16
#: lib/block_scout_web/templates/address/_balance_card.html.eex:13
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:16
#: lib/block_scout_web/templates/layout/app.html.eex:51
#: lib/block_scout_web/templates/layout/app.html.eex:66
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:146
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
msgstr "POA"
@ -408,7 +408,7 @@ msgid "Execute"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:28
#: lib/block_scout_web/templates/address/_balance_card.html.eex:24
msgid "Fetching tokens..."
msgstr ""
@ -431,7 +431,7 @@ msgid "GET"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:159
#: lib/block_scout_web/templates/transaction/overview.html.eex:194
msgid "Gas"
msgstr ""
@ -472,7 +472,7 @@ msgid "IN"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:47
#: lib/block_scout_web/templates/layout/app.html.eex:62
msgid "Indexing Tokens"
msgstr ""
@ -488,14 +488,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21
#: lib/block_scout_web/templates/address/_tabs.html.eex:90
#: lib/block_scout_web/templates/address/_tabs.html.eex:101
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:275
#: lib/block_scout_web/views/transaction_view.ex:258
#: lib/block_scout_web/views/address_view.ex:295
#: lib/block_scout_web/views/transaction_view.ex:339
msgid "Internal Transactions"
msgstr ""
@ -508,12 +508,12 @@ msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:48
#: lib/block_scout_web/templates/layout/app.html.eex:63
msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:171
#: lib/block_scout_web/templates/transaction/overview.html.eex:206
msgid "Limit"
msgstr ""
@ -521,19 +521,19 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:21
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:48
#: lib/block_scout_web/templates/transaction_log/index.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:259
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28
#: lib/block_scout_web/templates/layout/app.html.eex:49
#: lib/block_scout_web/views/address_view.ex:118
#: lib/block_scout_web/templates/layout/app.html.eex:64
#: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:113
#: lib/block_scout_web/views/transaction_view.ex:194
msgid "Max of"
msgstr ""
@ -603,7 +603,7 @@ msgid "Next Page"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
msgid "No"
msgstr ""
@ -661,8 +661,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
#: lib/block_scout_web/views/transaction_view.ex:127
#: lib/block_scout_web/views/transaction_view.ex:161
#: lib/block_scout_web/views/transaction_view.ex:208
#: lib/block_scout_web/views/transaction_view.ex:242
msgid "Pending"
msgstr ""
@ -679,13 +679,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:21
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/layout/app.html.eex:65
msgid "Price"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:88
#: lib/block_scout_web/templates/address/overview.html.eex:102
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -698,11 +698,11 @@ msgid "Query"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:65
#: lib/block_scout_web/templates/address/_tabs.html.eex:122
#: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/templates/address/_tabs.html.eex:142
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:277
#: lib/block_scout_web/views/address_view.ex:298
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
msgid "Reset"
msgstr ""
@ -728,16 +728,11 @@ msgid "Responses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
#: lib/block_scout_web/templates/layout/_topnav.html.eex:106
msgid "Search"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, transaction hash, or block number"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -767,7 +762,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:129
#: lib/block_scout_web/views/transaction_view.ex:210
msgid "Success"
msgstr ""
@ -877,8 +872,10 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction/overview.html.eex:148
#: lib/block_scout_web/templates/transaction/overview.html.eex:165
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:203
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Token Transfer"
msgstr ""
@ -890,18 +887,18 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:257
#: lib/block_scout_web/views/transaction_view.ex:338
msgid "Token Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:13
#: lib/block_scout_web/templates/address/_tabs.html.eex:85
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/address_token/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19
#: lib/block_scout_web/views/address_view.ex:273
#: lib/block_scout_web/views/address_view.ex:293
msgid "Tokens"
msgstr ""
@ -931,7 +928,7 @@ msgid "Total transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:206
#: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction"
msgstr ""
@ -953,7 +950,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:5
#: lib/block_scout_web/templates/address/_tabs.html.eex:80
#: lib/block_scout_web/templates/address/_tabs.html.eex:91
#: lib/block_scout_web/templates/address_transaction/index.html.eex:53
#: lib/block_scout_web/templates/address_validation/index.html.eex:14
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13
@ -962,7 +959,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:274
#: lib/block_scout_web/views/address_view.ex:294
msgid "Transactions"
msgstr ""
@ -998,7 +995,7 @@ msgid "Unique Token"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:164
#: lib/block_scout_web/templates/transaction/overview.html.eex:199
msgid "Used"
msgstr ""
@ -1019,7 +1016,7 @@ msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:146
#: lib/block_scout_web/templates/transaction/overview.html.eex:181
msgid "Value"
msgstr ""
@ -1029,7 +1026,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:122
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132
msgid "Verify & publish"
msgstr ""
@ -1089,12 +1086,12 @@ msgid "Wei"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:45
msgid "Yes"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:64
#: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at"
msgstr ""
@ -1170,7 +1167,7 @@ msgid "Loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130
msgid "Loading...."
msgstr ""
@ -1215,7 +1212,7 @@ msgid "This API is provided for developers transitioning their applications from
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:100
#: lib/block_scout_web/templates/transaction/overview.html.eex:99
msgid "Raw Input"
msgstr ""
@ -1404,8 +1401,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/views/address_view.ex:278
#: lib/block_scout_web/templates/address/_tabs.html.eex:107
#: lib/block_scout_web/views/address_view.ex:299
msgid "Coin Balance History"
msgstr ""
@ -1557,7 +1554,7 @@ msgid "Test Networks"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_footer.html.eex:94
#: lib/block_scout_web/templates/layout/_footer.html.eex:95
msgid "Version"
msgstr ""
@ -1587,87 +1584,163 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "2 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111
msgid "5 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68
msgid "Contract Libraries"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53
msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
#: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Last Balance Update: Block #"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:41
#: lib/block_scout_web/templates/address/overview.html.eex:48
msgid "Transactions Sent"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/overview.html.eex:90
msgid "Transaction Speed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122
#: lib/block_scout_web/templates/transaction/overview.html.eex:127
#: lib/block_scout_web/templates/transaction/overview.html.eex:121
#: lib/block_scout_web/templates/transaction/overview.html.eex:126
msgid "Hex (Default)"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:131
#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16
msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21
msgid "Run"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:43
msgid "Incoming Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:83
msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:87
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Version"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63
msgid "Enter constructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20
msgid "Copy Decompiled Contract Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:297
msgid "Decompiled Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:134
msgid "Decompiled code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18
msgid "Decompiled contract code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11
msgid "Decompiler version"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Optimization runs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:148
msgid "ERC-20"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:165
msgid "ERC-721"
msgstr ""

@ -44,6 +44,20 @@ defmodule BlockScoutWeb.ChainTest do
assert {:ok, %Address{hash: ^hash}} = address |> Phoenix.Param.to_param() |> Chain.from_param()
end
test "finds a token by its name" do
name = "AYR"
insert(:token, symbol: name)
assert {:ok, %Address{}} = name |> Chain.from_param()
end
test "finds a token by its name even if lowercase name was passed" do
name = "ayr"
insert(:token, symbol: String.upcase(name))
assert {:ok, %Address{}} = name |> Chain.from_param()
end
test "returns {:error, :not_found} when garbage is passed in" do
assert {:error, :not_found} = Chain.from_param("any ol' thing")
end

@ -6,6 +6,49 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
alias Explorer.Chain.{Transaction, Wei}
alias BlockScoutWeb.API.RPC.AddressController
describe "listaccounts" do
setup do
%{params: %{"module" => "account", "action" => "listaccounts"}}
end
test "with no addresses", %{params: params, conn: conn} do
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == []
end
test "with existing addresses", %{params: params, conn: conn} do
first_address = insert(:address, fetched_coin_balance: 10, inserted_at: Timex.shift(Timex.now(), minutes: -10))
second_address = insert(:address, fetched_coin_balance: 100, inserted_at: Timex.shift(Timex.now(), minutes: -5))
first_address_hash = to_string(first_address.hash)
second_address_hash = to_string(second_address.hash)
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert [
%{
"address" => ^first_address_hash,
"balance" => "10"
},
%{
"address" => ^second_address_hash,
"balance" => "100"
}
] = response["result"]
end
end
describe "balance" do
test "with missing address hash", %{conn: conn} do
params = %{
@ -724,34 +767,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK"
end
test "ignores pagination params if page is less than 1", %{conn: conn} do
address = insert(:address)
6
|> insert_list(:transaction, from_address: address)
|> with_block()
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
# page number
"page" => "0",
# page size
"offset" => "2"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 6
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "ignores pagination params if offset is less than 1", %{conn: conn} do
test "ignores offset param if offset is less than 1", %{conn: conn} do
address = insert(:address)
6
@ -778,7 +794,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK"
end
test "ignores pagination params if offset is over 10,000", %{conn: conn} do
test "ignores offset param if offset is over 10,000", %{conn: conn} do
address = insert(:address)
6

@ -1,5 +1,244 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Factory
describe "listcontracts" do
setup do
%{params: %{"module" => "contract", "action" => "listcontracts"}}
end
test "with an invalid filter value", %{conn: conn, params: params} do
response =
conn
|> get("/api", Map.put(params, "filter", "invalid"))
|> json_response(400)
assert response["message"] ==
"invalid is not a valid value for `filter`. Please use one of: verified, decompiled, unverified, not_decompiled, 1, 2, 3, 4."
assert response["status"] == "0"
end
test "with no contracts", %{conn: conn, params: params} do
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == []
end
test "with a verified smart contract, all contract information is shown", %{conn: conn, params: params} do
contract = insert(:smart_contract)
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => Jason.encode!(contract.abi),
"Address" => to_string(contract.address_hash),
"CompilerVersion" => contract.compiler_version,
"ContractName" => contract.name,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0"),
"SourceCode" => contract.contract_source_code
}
]
end
test "with an unverified contract address, only basic information is shown", %{conn: conn, params: params} do
address = insert(:contract_address)
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only unverified contracts shows only unverified contracts", %{params: params, conn: conn} do
address = insert(:contract_address)
insert(:smart_contract)
response =
conn
|> get("/api", Map.put(params, "filter", "unverified"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only verified contracts shows only verified contracts", %{params: params, conn: conn} do
insert(:contract_address)
contract = insert(:smart_contract)
response =
conn
|> get("/api", Map.put(params, "filter", "verified"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => Jason.encode!(contract.abi),
"Address" => to_string(contract.address_hash),
"CompilerVersion" => contract.compiler_version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"ContractName" => contract.name,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0"),
"SourceCode" => contract.contract_source_code
}
]
end
test "filtering for only decompiled contracts shows only decompiled contracts", %{params: params, conn: conn} do
insert(:contract_address)
decompiled_smart_contract = insert(:decompiled_smart_contract)
response =
conn
|> get("/api", Map.put(params, "filter", "decompiled"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(decompiled_smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => decompiled_smart_contract.decompiled_source_code,
"DecompilerVersion" => "test_decompiler",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only decompiled contracts, with a decompiled with version filter", %{params: params, conn: conn} do
insert(:decompiled_smart_contract, decompiler_version: "foobar")
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")
response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only decompiled contracts, with a decompiled with version filter, where another decompiled version exists",
%{params: params, conn: conn} do
non_match = insert(:decompiled_smart_contract, decompiler_version: "foobar")
insert(:decompiled_smart_contract, decompiler_version: "bizbuz", address_hash: non_match.address_hash)
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")
response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do
insert(:decompiled_smart_contract)
insert(:smart_contract)
contract_address = insert(:contract_address)
response =
conn
|> get("/api", Map.put(params, "filter", "not_decompiled"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
end
describe "getabi" do
test "with missing address hash", %{conn: conn} do
@ -119,11 +358,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [
%{
"Address" => "",
"SourceCode" => "",
"ABI" => "Contract source code not verified",
"ContractName" => "",
"CompilerVersion" => "",
"OptimizationUsed" => ""
"OptimizationUsed" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => ""
}
]
@ -148,13 +390,16 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [
%{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
# The contract's optimization value is true, so the expected value
# for `OptimizationUsed` is "1". If it was false, the expected value
# would be "0".
"DecompilerVersion" => "",
"OptimizationUsed" => "1"
}
]
@ -169,4 +414,100 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK"
end
end
describe "verify" do
test "with an address that doesn't exist", %{conn: conn} do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => contract_code_info.name,
"compilerVersion" => contract_code_info.version,
"optimization" => contract_code_info.optimized,
"contractSourceCode" => contract_code_info.source_code
}
response =
conn
|> get("/api", params)
|> json_response(200)
expected_result = %{
"Address" => to_string(contract_address.hash),
"SourceCode" => contract_code_info.source_code,
"ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "0"
}
assert response["status"] == "1"
assert response["result"] == expected_result
assert response["message"] == "OK"
end
test "with external libraries", %{conn: conn} do
contract_data =
"#{File.cwd!()}/test/support/fixture/smart_contract/compiler_tests.json"
|> File.read!()
|> Jason.decode!()
|> List.first()
%{
"compiler_version" => compiler_version,
"external_libraries" => external_libraries,
"name" => name,
"optimize" => optimize,
"contract" => contract_source_code,
"expected_bytecode" => expected_bytecode
} = contract_data
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => name,
"compilerVersion" => compiler_version,
"optimization" => optimize,
"contractSourceCode" => contract_source_code
}
params_with_external_libraries =
external_libraries
|> Enum.with_index()
|> Enum.reduce(params, fn {{name, address}, index}, acc ->
name_key = "library#{index + 1}Name"
address_key = "library#{index + 1}Address"
acc
|> Map.put(name_key, name)
|> Map.put(address_key, address)
end)
response =
conn
|> get("/api", params_with_external_libraries)
|> json_response(200)
assert response["status"] == "1"
assert response["message"] == "OK"
result = response["result"]
assert result["Address"] == to_string(contract_address.hash)
assert result["SourceCode"] == contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""
assert result["OptimizationUsed"] == "1"
end
end
end

@ -75,20 +75,4 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslatorTest do
assert json_response(result, 200) == %{}
end
end
test "translate_module/2" do
assert RPCTranslator.translate_module(%{"test" => __MODULE__}, "tesT") == {:ok, __MODULE__}
assert RPCTranslator.translate_module(%{}, "test") == :error
end
test "translate_action/1" do
expected = :test_atom
assert RPCTranslator.translate_action("test_atoM") == {:ok, expected}
assert RPCTranslator.translate_action("some_atom_that_should_not_exist") == :error
end
test "call_controller/3", %{conn: conn} do
assert RPCTranslator.call_controller(conn, TestController, :bad_action) == :error
assert {:ok, %Plug.Conn{}} = RPCTranslator.call_controller(conn, TestController, :test_action)
end
end

@ -367,6 +367,59 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
refute response["result"]
end
test "paginates logs", %{conn: conn} do
block = insert(:block, hash: "0x30d522bcf2d8e0cabc286e6e40623c475c3bc05d0ec484ea239c103b1ac0ded9", number: 99)
transaction =
:transaction
|> insert(hash: "0x13b6bb8e06322096dc83e8d7e6332ca19919ea642212cd259c6b20e7523a0599")
|> with_block(block, status: :ok)
address = insert(:address)
Enum.each(1..100, fn _ ->
insert(:log,
address: address,
transaction: transaction,
first_topic: "first topic",
second_topic: "second topic"
)
end)
params1 = %{
"module" => "transaction",
"action" => "gettxinfo",
"txhash" => "#{transaction.hash}"
}
assert response1 =
conn
|> get("/api", params1)
|> json_response(200)
assert response1["status"] == "1"
assert response1["message"] == "OK"
assert %{
"action" => "gettxinfo",
"index" => _,
"module" => "transaction",
"txhash" => _
} = response1["result"]["next_page_params"]
params2 = response1["result"]["next_page_params"]
assert response2 =
conn
|> get("/api", params2)
|> json_response(200)
assert response2["status"] == "1"
assert response2["message"] == "OK"
assert is_nil(response2["result"]["next_page_params"])
assert response1["result"]["logs"] != response2["result"]["logs"]
end
test "with a txhash with ok status", %{conn: conn} do
block = insert(:block)
@ -409,7 +462,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"data" => "#{log.data}",
"topics" => ["first topic", "second topic", nil, nil]
}
]
],
"next_page_params" => nil
}
assert response =

@ -0,0 +1,99 @@
defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Repo
alias Explorer.Chain.DecompiledSmartContract
import Ecto.Query,
only: [from: 2]
@secret "secret"
describe "when used authorized" do
setup %{conn: conn} = context do
Application.put_env(:block_scout_web, :decompiled_smart_contract_token, @secret)
auth_conn = conn |> put_req_header("auth_token", @secret)
{:ok, Map.put(context, :conn, auth_conn)}
end
test "returns unprocessable_entity status when params are invalid", %{conn: conn} do
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create))
assert request.status == 422
assert request.resp_body == "{\"error\":\"address_hash is invalid\"}"
end
test "returns unprocessable_entity when code is empty", %{conn: conn} do
decompiler_version = "test_decompiler"
address = insert(:address)
params = %{
"address_hash" => to_string(address.hash),
"decompiler_version" => decompiler_version
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 422
assert request.resp_body == "{\"decompiled_source_code\":\"can't be blank\"}"
end
test "can not update code for the same decompiler version", %{conn: conn} do
address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
insert(:decompiled_smart_contract,
address_hash: address_hash,
decompiler_version: decompiler_version,
decompiled_source_code: decompiled_source_code
)
params = %{
"address_hash" => address_hash,
"decompiler_version" => decompiler_version,
"decompiled_source_code" => decompiled_source_code
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 422
assert request.resp_body == "{\"error\":\"decompiled code already exists for the decompiler version\"}"
end
test "creates decompiled smart contract", %{conn: conn} do
address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
params = %{
"address_hash" => address_hash,
"decompiler_version" => decompiler_version,
"decompiled_source_code" => decompiled_source_code
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 201
assert request.resp_body ==
"{\"address_hash\":\"0x0000000000000000000000000000000000000001\",\"decompiler_version\":\"test_decompiler\",\"decompiled_source_code\":\"hello world\"}"
decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash))
assert to_string(decompiled_smart_contract.address_hash) == address_hash
assert decompiled_smart_contract.decompiler_version == decompiler_version
assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code
end
end
describe "when user is not authorized" do
test "returns forbedden", %{conn: conn} do
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create))
assert request.status == 403
end
end
end

@ -66,7 +66,27 @@ defmodule BlockScoutWeb.ChainControllerTest do
end
end
describe "GET q/2" do
describe "GET token_autocomplete/2" do
test "finds matching tokens" do
insert(:token, name: "MaGiC")
insert(:token, name: "Evil")
conn = get(conn(), "/token_autocomplete?q=magic")
assert Enum.count(json_response(conn, 200)) == 1
end
test "finds two matching tokens" do
insert(:token, name: "MaGiC")
insert(:token, name: "magic")
conn = get(conn(), "/token_autocomplete?q=magic")
assert Enum.count(json_response(conn, 200)) == 2
end
end
describe "GET search/2" do
test "finds a consensus block by block number", %{conn: conn} do
insert(:block, number: 37)
conn = get(conn, "/search?q=37")

@ -13,6 +13,8 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do
{:ok, bypass: bypass}
end
# wallaby with chrome headles always fails this test
@tag :skip
test "users validates smart contract", %{session: session, bypass: bypass} do
Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end)
@ -36,7 +38,8 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do
contract_name: name,
version: version,
optimization: false,
source_code: source_code
source_code: source_code,
evm_version: "byzantium"
})
|> ContractVerifyPage.verify_and_publish()
@ -48,7 +51,13 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do
session
|> ContractVerifyPage.visit_page("0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461")
|> ContractVerifyPage.fill_form(%{contract_name: "", version: nil, optimization: nil, source_code: ""})
|> ContractVerifyPage.fill_form(%{
contract_name: "",
version: nil,
optimization: nil,
source_code: "",
evm_version: "byzantium"
})
|> ContractVerifyPage.verify_and_publish()
|> assert_has(ContractVerifyPage.validation_error())
end

@ -13,7 +13,8 @@ defmodule BlockScoutWeb.ContractVerifyPage do
contract_name: contract_name,
version: version,
optimization: optimization,
source_code: source_code
source_code: source_code,
evm_version: evm_version
}) do
session
|> fill_in(css("[data-test='contract_name']"), with: contract_name)
@ -24,6 +25,11 @@ defmodule BlockScoutWeb.ContractVerifyPage do
_ -> click(session, option(version))
end
case evm_version do
nil -> nil
_ -> click(session, option(evm_version))
end
case optimization do
true ->
click(session, radio_button("Yes"))

@ -487,10 +487,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> AddressPage.visit_page(lincoln)
|> AddressPage.click_balance_dropdown_toggle()
|> AddressPage.fill_balance_dropdown_search("ato")
|> assert_has(AddressPage.token_balance(count: 1))
|> assert_has(AddressPage.token_type(count: 1))
|> assert_has(AddressPage.token_balance(count: 2))
|> assert_has(AddressPage.token_type(count: 2))
|> assert_has(AddressPage.token_type_count(type: "ERC-721", text: "1"))
|> assert_has(AddressPage.token_balance_counter("1"))
end
test "filter token balances by token symbol", %{session: session, lincoln: lincoln} do
@ -498,10 +497,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> AddressPage.visit_page(lincoln)
|> AddressPage.click_balance_dropdown_toggle()
|> AddressPage.fill_balance_dropdown_search("T2")
|> assert_has(AddressPage.token_balance(count: 1))
|> assert_has(AddressPage.token_type(count: 1))
|> assert_has(AddressPage.token_balance(count: 2))
|> assert_has(AddressPage.token_type(count: 2))
|> assert_has(AddressPage.token_type_count(type: "ERC-20", text: "1"))
|> assert_has(AddressPage.token_balance_counter("1"))
end
test "reset token balances filter when dropdown closes", %{session: session, lincoln: lincoln} do
@ -509,7 +507,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> AddressPage.visit_page(lincoln)
|> AddressPage.click_balance_dropdown_toggle()
|> AddressPage.fill_balance_dropdown_search("ato")
|> assert_has(AddressPage.token_balance_counter("1"))
|> AddressPage.click_outside_of_the_dropdown()
|> assert_has(AddressPage.token_balance_counter("2"))
end

@ -215,7 +215,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"r" => to_string(transaction.r),
"s" => to_string(transaction.s),
"status" => nil,
"v" => transaction.v,
"v" => to_string(transaction.v),
"value" => to_string(transaction.value.value),
"from_address_hash" => to_string(transaction.from_address_hash),
"to_address_hash" => to_string(transaction.to_address_hash),

@ -55,7 +55,7 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
"r" => to_string(transaction.r),
"s" => to_string(transaction.s),
"status" => transaction.status |> to_string() |> String.upcase(),
"v" => transaction.v,
"v" => to_string(transaction.v),
"value" => to_string(transaction.value.value),
"from_address_hash" => to_string(transaction.from_address_hash),
"to_address_hash" => to_string(transaction.to_address_hash),

@ -0,0 +1,74 @@
defmodule BlockScoutWeb.AddressDecompiledContractViewTest do
use Explorer.DataCase
alias BlockScoutWeb.AddressDecompiledContractView
describe "highlight_decompiled_code/1" do
test "generate correct html code" do
code = """
#
# eveem.org 6 Feb 2019
# Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875
#
# Let's make the world open source
# 
#
# I failed with these:
# - unknowne77c646d(?)
# - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)
# All the rest is below.
#
# Storage definitions and getters
def storage:
allowance is uint256 => uint256 # mask(256, 0) at storage #2
stor4 is uint256 => uint8 # mask(8, 0) at storage #4
def allowance(address _owner, address _spender) payable: 
require (calldata.size - 4) >= 64
return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]
#
# Regular functions - see Tutorial for understanding quirks of the code
#
# folder failed in this function - may be terribly long, sorry
def unknownc47d033b(?) payable: 
if (calldata.size - 4) < 32:
revert
else:
if not (320 - 1) or not cd[4]:
revert
else:
mem[0] = (320 - 1) and (320 - 1) and cd[4]
mem[32] = 4
mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])
return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])
def _fallback() payable: # default function
revert
"""
result = AddressDecompiledContractView.highlight_decompiled_code(code)
assert result ==
"<code> <span style=\"color:rgb(111, 110, 111)\">#</code>\n<code> # eveem.org 6 Feb 2019</code>\n<code> # Decompiled source of </span>0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875<span style=\"color:rgb(111, 110, 111)\"></code>\n<code> #</code>\n<code> # Let's make the world open source</code>\n<code> # </span></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</code>\n<code> # I failed with these:</code>\n<code> </span><span style=\"color:rgb(111, 110, 111)\"># - </span><span style=\"color:rgb(236, 89, 58)\">unknowne77c646d(?)</span><span style=\"color:rgb(111, 110, 111)\"></code>\n<code> </span><span style=\"color:rgb(111, 110, 111)\"># - </span><span style=\"color:rgb(236, 89, 58)\">transferFromWithData(address _from, address _to, uint256 _value, bytes _data)</span><span style=\"color:rgb(111, 110, 111)\"></code>\n<code> # All the rest is below.</code>\n<code> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># Storage definitions and getters</span></code>\n<code></code>\n<code> <span style=\"color:rgb(57, 115, 0)\">def</span> storage:</code>\n<code> <span style=\"color:rgb(57, 115, 0)\">allowance</span> is uint256 => uint256 <span style=\"color:rgb(111, 110, 111)\"># mask(256, 0) at storage #2</span></code>\n<code> <span style=\"color:rgb(57, 115, 0)\">stor4</span> is uint256 => uint8 <span style=\"color:rgb(111, 110, 111)\"># mask(8, 0) at storage #4</span></code>\n<code></code>\n<code> <span style=\"color:rgb(136, 0, 0)\">def </span>allowance(address <span style=\"color:rgb(57, 115, 0)\">_owner</span>, address <span style=\"color:rgb(57, 115, 0)\">_spender</span>) <span style=\"color:rgb(136, 0, 0)\">payable</span>: <span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code> require (calldata.size - 4)<span style=\"font-weight:bold\"> >= </span>64</code>\n<code> return <span style=\"color:rgb(57, 115, 0)\">allowance</span><span style=\"color:rgb(57, 115, 0)\">[</span>sha3(((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span><span style=\"color:rgb(57, 115, 0)\">_owner</span>), 1), ((320 - 1)<span style=\"font-weight:bold\"> and </span><span style=\"color:rgb(57, 115, 0)\">_spender</span><span style=\"font-weight:bold\"> and </span>(320 - 1))<span style=\"color:rgb(57, 115, 0)\">]</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</code>\n<code> # Regular functions - see Tutorial for understanding quirks of the code</code>\n<code> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># folder failed in this function - may be terribly long, sorry</span></code>\n<code> <span style=\"color:rgb(136, 0, 0)\">def </span>unknownc47d033b(?) <span style=\"color:rgb(136, 0, 0)\">payable</span>: <span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code> if (calldata.size - 4)<span style=\"font-weight:bold\"> < </span>32:</code>\n<code> revert</code>\n<code> else:</code>\n<code> if not (320 - 1)<span style=\"font-weight:bold\"> or </span>not cd[4]:</code>\n<code> revert</code>\n<code> else:</code>\n<code> <span style=\"color:rgb(136, 0, 0)\">mem[</span>0<span style=\"color:rgb(136, 0, 0)\">]</span> = (320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4]</code>\n<code> <span style=\"color:rgb(136, 0, 0)\">mem[</span>32<span style=\"color:rgb(136, 0, 0)\">]</span> = 4</code>\n<code> <span style=\"color:rgb(136, 0, 0)\">mem[</span>96<span style=\"color:rgb(136, 0, 0)\">]</span> = bool(<span style=\"color:rgb(57, 115, 0)\">stor4</span><span style=\"color:rgb(57, 115, 0)\">[</span>((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4])<span style=\"color:rgb(57, 115, 0)\">]</span>)</code>\n<code> return bool(<span style=\"color:rgb(57, 115, 0)\">stor4</span><span style=\"color:rgb(57, 115, 0)\">[</span>((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4])<span style=\"color:rgb(57, 115, 0)\">]</span>)</code>\n<code></code>\n<code> <span style=\"color:rgb(136, 0, 0)\">def </span>_fallback() <span style=\"color:rgb(136, 0, 0)\">payable</span>: <span style=\"color:rgb(111, 110, 111)\"># default function</span></code>\n<code> revert</code>\n<code></code>\n"
end
end
describe "sort_contracts_by_version/1" do
test "sorts contracts in lexicographical order" do
contract2 = insert(:decompiled_smart_contract, decompiler_version: "v2")
contract1 = insert(:decompiled_smart_contract, decompiler_version: "v1")
contract3 = insert(:decompiled_smart_contract, decompiler_version: "v3")
result = AddressDecompiledContractView.sort_contracts_by_version([contract2, contract1, contract3])
assert result == [contract3, contract2, contract1]
end
end
end

@ -60,4 +60,30 @@ defmodule BlockScoutWeb.LayoutViewTest do
assert LayoutView.network_title() == "POA"
end
end
describe "release_link/1" do
test "use the version when there is no release_link env configured for it" do
Application.put_env(:block_scout_web, :release_link, nil)
assert LayoutView.release_link("1.3.4") == "1.3.4"
end
test "use the version when empty release_link env configured for it" do
Application.put_env(:block_scout_web, :release_link, "")
assert LayoutView.release_link("1.3.4") == "1.3.4"
end
test "use the enviroment release link when it's configured" do
Application.put_env(
:block_scout_web,
:release_link,
"https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta"
)
assert LayoutView.release_link("1.3.4") ==
{:safe,
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">1.3.4</a>)}
end
end
end

@ -47,6 +47,30 @@ defmodule BlockScoutWeb.TransactionViewTest do
end
end
describe "erc721_token_transfer/2" do
test "finds token transfer" do
from_address_hash = "0x7a30272c902563b712245696f0a81c5a0e45ddc8"
to_address_hash = "0xb544cead8b660aae9f2e37450f7be2ffbc501793"
from_address = insert(:address, hash: from_address_hash)
to_address = insert(:address, hash: to_address_hash)
block = insert(:block)
transaction =
insert(:transaction,
input:
"0x23b872dd0000000000000000000000007a30272c902563b712245696f0a81c5a0e45ddc8000000000000000000000000b544cead8b660aae9f2e37450f7be2ffbc5017930000000000000000000000000000000000000000000000000000000000000002",
value: Decimal.new(0),
created_contract_address_hash: nil
)
|> with_block(block, status: :ok)
token_transfer =
insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction)
assert TransactionView.erc721_token_transfer(transaction, [token_transfer]) == token_transfer
end
end
describe "processing_time_duration/2" do
test "returns :pending if the transaction has no block" do
transaction = build(:transaction, block: nil)

File diff suppressed because one or more lines are too long

@ -9,6 +9,14 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
wait_per_timeout: :timer.seconds(20),
max_jitter: :timer.seconds(2)
# Add this configuration to add global RPC request throttling.
# throttle_rate_limit: 250,
# throttle_rolling_window_opts: [
# window_count: 4,
# duration: :timer.seconds(15),
# table: EthereumJSONRPC.RequestCoordinator.ThrottleCounter
# ]
config :ethereum_jsonrpc, EthereumJSONRPC.Tracer,
service: :ethereum_jsonrpc,
adapter: SpandexDatadog.Adapter,

@ -7,7 +7,14 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
],
wait_per_timeout: 2,
max_jitter: 1
max_jitter: 1,
# This should not actually limit anything in tests, but it is here to enable the relevant code for testing
throttle_rate_limit: 10_000,
throttle_rolling_window_opts: [
window_count: 4,
duration: :timer.seconds(1),
table: EthereumJSONRPC.RequestCoordinator.ThrottleCounter
]
config :ethereum_jsonrpc, EthereumJSONRPC.Tracer, disabled?: false

@ -28,6 +28,7 @@ defmodule EthereumJSONRPC do
alias EthereumJSONRPC.{
Block,
Blocks,
Contract,
FetchedBalances,
FetchedBeneficiaries,
FetchedCodes,
@ -160,33 +161,9 @@ defmodule EthereumJSONRPC do
}
]}
"""
@spec execute_contract_functions(
[%{contract_address: String.t(), data: String.t(), id: String.t()}],
json_rpc_named_arguments,
[{:block_number, non_neg_integer()}]
) :: {:ok, list()} | {:error, term()}
def execute_contract_functions(functions, json_rpc_named_arguments, opts \\ []) do
block_number = Keyword.get(opts, :block_number)
functions
|> Enum.map(&build_eth_call_payload(&1, block_number))
|> json_rpc(json_rpc_named_arguments)
end
defp build_eth_call_payload(
%{contract_address: address, data: data, id: id},
nil = _block_number
) do
params = [%{to: address, data: data}, "latest"]
request(%{id: id, method: "eth_call", params: params})
end
defp build_eth_call_payload(
%{contract_address: address, data: data, id: id},
block_number
) do
params = [%{to: address, data: data}, integer_to_quantity(block_number)]
request(%{id: id, method: "eth_call", params: params})
@spec execute_contract_functions([Contract.call()], [map()], json_rpc_named_arguments) :: [Contract.call_result()]
def execute_contract_functions(functions, abi, json_rpc_named_arguments) do
Contract.execute_contract_functions(functions, abi, json_rpc_named_arguments)
end
@doc """
@ -287,6 +264,16 @@ defmodule EthereumJSONRPC do
)
end
@doc """
Fetches internal transactions for entire blocks from variant API.
"""
def fetch_block_internal_transactions(params_list, json_rpc_named_arguments) when is_list(params_list) do
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions(
params_list,
json_rpc_named_arguments
)
end
@doc """
Fetches pending transactions from variant API.
"""
@ -339,11 +326,18 @@ defmodule EthereumJSONRPC do
@doc """
Converts `t:quantity/0` to `t:non_neg_integer/0`.
"""
@spec quantity_to_integer(quantity) :: non_neg_integer()
@spec quantity_to_integer(quantity) :: non_neg_integer() | :error
def quantity_to_integer("0x" <> hexadecimal_digits) do
String.to_integer(hexadecimal_digits, 16)
end
def quantity_to_integer(string) do
case Integer.parse(string) do
{integer, ""} -> integer
_ -> :error
end
end
@doc """
Converts `t:non_neg_integer/0` to `t:quantity/0`
"""
@ -411,9 +405,13 @@ defmodule EthereumJSONRPC do
Converts `t:timestamp/0` to `t:DateTime.t/0`
"""
def timestamp_to_datetime(timestamp) do
timestamp
|> quantity_to_integer()
|> Timex.from_unix()
case quantity_to_integer(timestamp) do
:error ->
nil
quantity ->
Timex.from_unix(quantity)
end
end
defp fetch_blocks_by_params(params, request, json_rpc_named_arguments)

@ -9,16 +9,32 @@ defmodule EthereumJSONRPC.Application do
@impl Application
def start(_type, _args) do
rolling_window_opts =
:ethereum_jsonrpc
|> Application.fetch_env!(RequestCoordinator)
|> Keyword.fetch!(:rolling_window_opts)
config = Application.fetch_env!(:ethereum_jsonrpc, RequestCoordinator)
children = [
rolling_window_opts = Keyword.fetch!(config, :rolling_window_opts)
[
:hackney_pool.child_spec(:ethereum_jsonrpc, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000),
{RollingWindow, [rolling_window_opts]}
Supervisor.child_spec({RollingWindow, [rolling_window_opts]}, id: RollingWindow.ErrorThrottle)
]
|> add_throttle_rolling_window(config)
|> Supervisor.start_link(strategy: :one_for_one, name: EthereumJSONRPC.Supervisor)
end
defp add_throttle_rolling_window(children, config) do
if config[:throttle_rate_limit] do
case Keyword.fetch(config, :throttle_rolling_window_opts) do
{:ok, throttle_rolling_window_opts} ->
child =
Supervisor.child_spec({RollingWindow, [throttle_rolling_window_opts]}, id: RollingWindow.ThrottleRateLimit)
[child | children]
Supervisor.start_link(children, strategy: :one_for_one, name: EthereumJSONRPC.Supervisor)
:error ->
raise "If you have configured `:throttle_rate_limit` you must also configure `:throttle_rolling_window_opts`"
end
else
children
end
end
end

@ -431,7 +431,8 @@ defmodule EthereumJSONRPC.Block do
Enum.into(block, %{}, &entry_to_elixir/1)
end
defp entry_to_elixir({key, quantity}) when key in ~w(difficulty gasLimit gasUsed number size totalDifficulty) do
defp entry_to_elixir({key, quantity})
when key in ~w(difficulty gasLimit gasUsed minimumGasPrice number size totalDifficulty) do
{key, quantity_to_integer(quantity)}
end

@ -0,0 +1,95 @@
defmodule EthereumJSONRPC.Contract do
@moduledoc """
Smart contract functions executed by `eth_call`.
"""
import EthereumJSONRPC, only: [integer_to_quantity: 1, json_rpc: 2, request: 1]
alias EthereumJSONRPC.Encoder
@typedoc """
Call to a smart contract function.
* `:block_number` - the block in which to execute the function. Defaults to the `nil` to indicate
the latest block as determined by the remote node, which may differ from the latest block number
in `Explorer.Chain`.
"""
@type call :: %{
required(:contract_address) => String.t(),
required(:function_name) => String.t(),
required(:args) => [term()],
optional(:block_number) => EthereumJSONRPC.block_number()
}
@typedoc """
Result of calling a smart contract function.
"""
@type call_result :: {:ok, term()} | {:error, String.t()}
@spec execute_contract_functions([call()], [map()], EthereumJSONRPC.json_rpc_named_arguments()) :: [call_result()]
def execute_contract_functions(requests, abi, json_rpc_named_arguments) do
functions =
abi
|> ABI.parse_specification()
|> Enum.into(%{}, &{&1.function, &1})
requests_with_index = Enum.with_index(requests)
indexed_responses =
requests_with_index
|> Enum.map(fn {%{contract_address: contract_address, function_name: function_name, args: args} = request, index} ->
functions[function_name]
|> Encoder.encode_function_call(args)
|> eth_call_request(contract_address, index, Map.get(request, :block_number))
end)
|> json_rpc(json_rpc_named_arguments)
|> case do
{:ok, responses} -> responses
{:error, {:bad_gateway, _request_url}} -> raise "Bad gateway"
{:error, reason} when is_atom(reason) -> raise Atom.to_string(reason)
{:error, error} -> raise error
end
|> Enum.into(%{}, &{&1.id, &1})
Enum.map(requests_with_index, fn {%{function_name: function_name}, index} ->
indexed_responses[index]
|> case do
nil ->
{:error, "No result"}
response ->
{^index, result} = Encoder.decode_result(response, functions[function_name])
result
end
end)
rescue
error ->
Enum.map(requests, fn _ -> format_error(error) end)
end
defp eth_call_request(data, contract_address, id, block_number) do
block =
case block_number do
nil -> "latest"
block_number -> integer_to_quantity(block_number)
end
request(%{
id: id,
method: "eth_call",
params: [%{to: contract_address, data: data}, block]
})
end
defp format_error(message) when is_binary(message) do
{:error, message}
end
defp format_error(%{message: error_message}) do
format_error(error_message)
end
defp format_error(error) do
format_error(Exception.message(error))
end
end

@ -6,49 +6,19 @@ defmodule EthereumJSONRPC.Encoder do
alias ABI.TypeDecoder
@doc """
Given an ABI and a set of functions, returns the data the blockchain expects.
"""
@spec encode_abi([map()], %{String.t() => [any()]}) :: map()
def encode_abi(abi, functions) do
abi
|> ABI.parse_specification()
|> get_selectors(functions)
|> Enum.map(&encode_function_call/1)
|> Map.new()
end
@doc """
Given a list of function selectors from the ABI lib, and a list of functions names with their arguments, returns a list of selectors with their functions.
"""
@spec get_selectors([%ABI.FunctionSelector{}], %{String.t() => [term()]}) :: [{%ABI.FunctionSelector{}, [term()]}]
def get_selectors(abi, functions) do
Enum.map(functions, fn {function_name, args} ->
{get_selector_from_name(abi, function_name), args}
end)
end
@doc """
Given a list of function selectors from the ABI lib, and a function name, get the selector for that function.
"""
@spec get_selector_from_name([%ABI.FunctionSelector{}], String.t()) :: %ABI.FunctionSelector{}
def get_selector_from_name(abi, function_name) do
Enum.find(abi, fn selector -> function_name == selector.function end)
end
@doc """
Given a function selector and a list of arguments, returns their encoded versions.
This is what is expected on the Json RPC data parameter.
"""
@spec encode_function_call({%ABI.FunctionSelector{}, [term()]}) :: {String.t(), String.t()}
def encode_function_call({function_selector, args}) do
@spec encode_function_call(%ABI.FunctionSelector{}, [term()]) :: String.t()
def encode_function_call(function_selector, args) do
encoded_args =
function_selector
|> ABI.encode(parse_args(args))
|> Base.encode16(case: :lower)
{function_selector.function, "0x" <> encoded_args}
"0x" <> encoded_args
end
defp parse_args(args) do
@ -62,39 +32,16 @@ defmodule EthereumJSONRPC.Encoder do
end)
end
@doc """
Given a result set from the blockchain, and the functions selectors, returns the results decoded.
This functions assumes the result["id"] is the name of the function the result is for.
"""
@spec decode_abi_results([map()], [map()], %{String.t() => [any()]}) :: map()
def decode_abi_results(results, abi, functions) do
selectors =
abi
|> ABI.parse_specification()
|> get_selectors(functions)
|> Enum.map(fn {selector, _args} -> selector end)
results
|> Stream.map(&join_result_and_selector(&1, selectors))
|> Stream.map(&decode_result/1)
|> Map.new()
end
defp join_result_and_selector(result, selectors) do
{result, Enum.find(selectors, &(&1.function == result[:id]))}
end
@doc """
Given a result from the blockchain, and the function selector, returns the result decoded.
"""
@spec decode_result({map(), %ABI.FunctionSelector{}}) ::
@spec decode_result(map(), %ABI.FunctionSelector{}) ::
{String.t(), {:ok, any()} | {:error, String.t() | :invalid_data}}
def decode_result({%{error: %{code: code, message: message}, id: id}, _selector}) do
def decode_result(%{error: %{code: code, message: message}, id: id}, _selector) do
{id, {:error, "(#{code}) #{message}"}}
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)
decoded_data =

@ -21,6 +21,14 @@ defmodule EthereumJSONRPC.Ganache do
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Internal transaction fetching is not currently supported for Ganache.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore
@doc """
Pending transaction fetching is not supported currently for Ganache.

@ -32,6 +32,14 @@ defmodule EthereumJSONRPC.Geth do
end
end
@doc """
Internal transaction fetching for entire blocks is not currently supported for Geth.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore
@doc """
Pending transaction fetching is not supported currently for Geth.

@ -68,6 +68,9 @@ defmodule EthereumJSONRPC.HTTP do
chunked_json_rpc(tail, options, [decoded_body | decoded_response_bodies])
end
{:error, :timeout} ->
rechunk_json_rpc(chunks, options, :timeout, decoded_response_bodies)
{:error, _} = error ->
error
end

@ -26,18 +26,26 @@ defmodule EthereumJSONRPC.Parity do
end
end
@doc """
Internal transaction fetching for individual transactions is no longer supported for Parity.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
id_to_params = id_to_params(transactions_params)
def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
id_to_params = id_to_params(block_numbers)
with {:ok, responses} <-
id_to_params
|> trace_replay_transaction_requests()
|> trace_replay_block_transactions_requests()
|> json_rpc(json_rpc_named_arguments) do
trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params)
trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
end
end
@ -68,9 +76,9 @@ defmodule EthereumJSONRPC.Parity do
Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
end
defp trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params)
defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
with {:ok, traces} <- trace_replay_transaction_responses_to_traces(responses, id_to_params) do
with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
params =
traces
|> Traces.to_elixir()
@ -80,10 +88,10 @@ defmodule EthereumJSONRPC.Parity do
end
end
defp trace_replay_transaction_responses_to_traces(responses, id_to_params)
defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&trace_replay_transaction_response_to_traces(&1, id_to_params))
|> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params))
|> Enum.reduce(
{:ok, []},
fn
@ -115,48 +123,48 @@ defmodule EthereumJSONRPC.Parity do
end
end
defp trace_replay_transaction_response_to_traces(%{id: id, result: %{"trace" => traces}}, id_to_params)
when is_list(traces) and is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} =
Map.fetch!(id_to_params, id)
defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
when is_list(results) and is_map(id_to_params) do
block_number = Map.fetch!(id_to_params, id)
annotated_traces =
traces
results
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockNumber" => block_number,
"index" => index,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
|> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
traces
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockNumber" => block_number,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index,
"index" => index
})
end)
end)
{:ok, annotated_traces}
end
defp trace_replay_transaction_response_to_traces(%{id: id, error: error}, id_to_params)
defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
when is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} =
Map.fetch!(id_to_params, id)
block_number = Map.fetch!(id_to_params, id)
annotated_error =
Map.put(error, :data, %{
"blockNumber" => block_number,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
"blockNumber" => block_number
})
{:error, annotated_error}
end
defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
trace_replay_transaction_request(%{id: id, hash_data: hash_data})
defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, block_number} ->
trace_replay_block_transactions_request(%{id: id, block_number: block_number})
end)
end
defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
end
end

@ -274,10 +274,10 @@ defmodule EthereumJSONRPC.Receipt do
defp entry_to_elixir({"status" = key, status}) do
case status do
"0x0" ->
zero when zero in ["0x0", "0x00"] ->
{:ok, {key, :error}}
"0x1" ->
one when one in ["0x1", "0x01"] ->
{:ok, {key, :ok}}
# pre-Byzantium / Ethereum Classic on Parity

@ -18,9 +18,14 @@ defmodule EthereumJSONRPC.RequestCoordinator do
the tracked window
* `:max_jitter` - Maximimum amount of time in milliseconds to be added to each
wait before multiplied by timeout count
* `:throttle_rolling_window_opts` - Options for the process tracking all requests
* `:window_count` - Number of windows
* `:duration` - Total amount of time to coumt events in milliseconds
* `:table` - name of the ets table to store the data in
* `:throttle_rate_limit` - The total number of requests allowed in the all windows.
See the docs for `EthereumJSONRPC.RollingWindow` for more documentation for
`:rolling_window_opts`.
`:rolling_window_opts` and `:throttle_rolling_window_opts`.
This is how the wait time for each request is calculated:
@ -33,13 +38,20 @@ defmodule EthereumJSONRPC.RequestCoordinator do
config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
rolling_window_opts: [
window_count: 6,
duration: :timer.seconds(10),
duration: :timer.minutes(1),
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
],
wait_per_timeout: :timer.seconds(10),
max_jitter: :timer.seconds(1)
throttle_rate_limit: 60,
throttle_rolling_window_opts: [
window_count: 3,
duration: :timer.seconds(10),
table: EthereumJSONRPC.RequestCoordinator.RequestCounter
]
With this configuration, timeouts are tracked for 6 windows of 10 seconds for a total of 1 minute.
Requests are tracked for 3 windows of 10 seconds, for a total of 30 seconds, and
"""
require EthereumJSONRPC.Tracer
@ -47,6 +59,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
alias EthereumJSONRPC.{RollingWindow, Tracer, Transport}
@error_key :throttleable_error_count
@throttle_key :throttle_requests_count
@doc """
Performs a JSON RPC request and adds necessary backoff.
@ -64,12 +77,19 @@ defmodule EthereumJSONRPC.RequestCoordinator do
if sleep_time <= throttle_timeout do
:timer.sleep(sleep_time)
trace_request(request, fn ->
request
|> transport.json_rpc(transport_options)
|> handle_transport_response()
end)
remaining_wait_time = throttle_timeout - sleep_time
case throttle_request(remaining_wait_time) do
:ok ->
trace_request(request, fn ->
request
|> transport.json_rpc(transport_options)
|> handle_transport_response()
end)
:error ->
{:error, :timeout}
end
else
:timer.sleep(throttle_timeout)
@ -91,33 +111,78 @@ defmodule EthereumJSONRPC.RequestCoordinator do
defp handle_transport_response({:error, {:bad_gateway, _}} = error) do
RollingWindow.inc(table(), @error_key)
inc_throttle_table()
error
end
defp handle_transport_response({:error, :timeout} = error) do
RollingWindow.inc(table(), @error_key)
inc_throttle_table()
error
end
defp handle_transport_response(response), do: response
defp handle_transport_response(response) do
inc_throttle_table()
response
end
defp inc_throttle_table do
if config(:throttle_rolling_window_opts) do
RollingWindow.inc(throttle_table(), @throttle_key)
end
end
defp throttle_request(
remaining_time,
rate_limit \\ config(:throttle_rate_limit),
opts \\ config(:throttle_rolling_window_opts)
) do
if opts[:throttle_rate_limit] && RollingWindow.count(throttle_table(), @throttle_key) >= rate_limit do
if opts[:duration] >= remaining_time do
:timer.sleep(remaining_time)
:error
else
new_remaining_time = remaining_time - opts[:duration]
:timer.sleep(opts[:duration])
throttle_request(new_remaining_time, rate_limit, opts)
end
else
:ok
end
end
defp sleep_time do
wait_coefficient = RollingWindow.count(table(), @error_key)
jitter = :rand.uniform(config(:max_jitter))
wait_per_timeout = config(:wait_per_timeout)
jitter = :rand.uniform(config!(:max_jitter))
wait_per_timeout = config!(:wait_per_timeout)
wait_coefficient * (wait_per_timeout + jitter)
end
defp table do
:rolling_window_opts
|> config()
|> config!()
|> Keyword.fetch!(:table)
end
defp config(key) do
defp throttle_table do
case config(:throttle_rolling_window_opts) do
nil -> :ignore
keyword -> Keyword.fetch!(keyword, :table)
end
end
defp config!(key) do
:ethereum_jsonrpc
|> Application.get_env(__MODULE__)
|> Keyword.fetch!(key)
end
defp config(key) do
:ethereum_jsonrpc
|> Application.get_env(__MODULE__)
|> Keyword.get(key)
end
end

@ -149,23 +149,25 @@ defmodule EthereumJSONRPC.Transaction do
elixir_to_params(%{transaction | "input" => "0x"})
end
def elixir_to_params(%{
"blockHash" => block_hash,
"blockNumber" => block_number,
"from" => from_address_hash,
"gas" => gas,
"gasPrice" => gas_price,
"hash" => hash,
"input" => input,
"nonce" => nonce,
"r" => r,
"s" => s,
"to" => to_address_hash,
"transactionIndex" => index,
"v" => v,
"value" => value
}) do
%{
def elixir_to_params(
%{
"blockHash" => block_hash,
"blockNumber" => block_number,
"from" => from_address_hash,
"gas" => gas,
"gasPrice" => gas_price,
"hash" => hash,
"input" => input,
"nonce" => nonce,
"r" => r,
"s" => s,
"to" => to_address_hash,
"transactionIndex" => index,
"v" => v,
"value" => value
} = transaction
) do
result = %{
block_hash: block_hash,
block_number: block_number,
from_address_hash: from_address_hash,
@ -182,6 +184,12 @@ defmodule EthereumJSONRPC.Transaction do
value: value,
transaction_index: index
}
if transaction["creates"] do
Map.put(result, :created_contract_address_hash, transaction["creates"])
else
result
end
end
# Ganache bug. it return `to: "0x0"` except of `to: null`

@ -56,7 +56,8 @@ defmodule EthereumJSONRPC.Transactions do
to_address_hash: nil,
v: "0xbd",
value: 0,
transaction_index: 0
transaction_index: 0,
created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4"
}
]

@ -43,6 +43,22 @@ defmodule EthereumJSONRPC.Variant do
EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore
@doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the variant of the Ethereum JSONRPC API.
Uses API for fetching all internal transactions in the block
## Returns
* `{:ok, [internal_transaction_params]}` - internal transactions were successfully fetched for all blocks
* `{:error, reason}` - there was one or more errors with `reason` in fetching at least one of the blocks'
internal transactions
* `:ignore` - the variant does not support fetching internal transactions.
"""
@callback fetch_block_internal_transactions(
[EthereumJSONRPC.block_number()],
EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore
@doc """
Fetch the `t:Explorer.Chain.Transaction.changeset/2` params for pending transactions from the variant of the Ethereum
JSONRPC API.

@ -37,14 +37,14 @@ defmodule EthereumJSONRPC.WebSocket do
@doc """
Allow `c:start_link/1` to be called as part of a supervision tree.
"""
@callback child_spec([url :: String.t() | options :: term()]) :: Supervisor.child_spec()
@callback child_spec([(url :: String.t()) | (options :: term())]) :: Supervisor.child_spec()
@doc """
Starts web socket attached to `url` with `options`.
"""
# Return is same as `t:GenServer.on_start/0`
@callback start_link([url :: String.t() | options :: term()]) ::
{:ok, pid()} | :ignore | {:error, {:already_started, pid()} | reason :: term()}
@callback start_link([(url :: String.t()) | (options :: term())]) ::
{:ok, pid()} | :ignore | {:error, {:already_started, pid()} | (reason :: term())}
@doc """
Run a single Remote Procedure Call (RPC) `t:EthereumJSONRPC.Transport.request/0` through `t:web_socket/0`.

@ -50,7 +50,9 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
@impl WebSocket
# only allow secure WSS
def start_link(["wss://" <> _ = url, gen_fsm_options]) when is_list(gen_fsm_options) do
def start_link(["wss://" <> _ = url, websocket_opts, gen_fsm_options]) when is_list(gen_fsm_options) do
keepalive = websocket_opts[:keepalive]
fsm_name =
case Keyword.fetch(gen_fsm_options, :name) do
{:ok, name} when is_atom(name) -> {:local, name}
@ -68,6 +70,7 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
__MODULE__,
url,
ssl_verify: :verify_peer,
keepalive: keepalive,
socket_opts: [
cacerts: :certifi.cacerts(),
depth: 99,
@ -78,7 +81,9 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
)
end
def start_link(["ws://" <> _ = url, gen_fsm_options]) when is_list(gen_fsm_options) do
def start_link(["ws://" <> _ = url, websocket_opts, gen_fsm_options]) when is_list(gen_fsm_options) do
keepalive = websocket_opts[:keepalive]
fsm_name =
case Keyword.fetch(gen_fsm_options, :name) do
{:ok, name} when is_atom(name) -> {:local, name}
@ -90,7 +95,7 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
url,
__MODULE__,
url,
[]
keepalive: keepalive
)
end

@ -0,0 +1,12 @@
defmodule EthereumJSONRPC.RSK do
@moduledoc """
Ethereum JSONRPC methods that are/are not supported by [RSK](https://www.rsk.co/).
"""
@behaviour EthereumJSONRPC.Variant
def fetch_internal_transactions(_, _), do: :ignore
def fetch_pending_transactions(_), do: :ignore
def fetch_block_internal_transactions(_block_numbers, _json_rpc_named_arguments), do: :ignore
def fetch_beneficiaries(_, _), do: :ignore
end

@ -15,7 +15,7 @@ defmodule EthereumJsonrpc.MixProject do
plt_add_apps: [:mix],
ignore_warnings: "../../.dialyzer-ignore"
],
elixir: "~> 1.6",
elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
preferred_cli_env: [
@ -80,7 +80,7 @@ defmodule EthereumJsonrpc.MixProject do
# Tracing
{:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true},
# `:spandex` integration with Datadog
{:spandex_datadog, "~> 0.3.1"},
{:spandex_datadog, "~> 0.4.0"},
# Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.4"},
# Encode/decode function names and arguments

@ -0,0 +1,134 @@
defmodule EthereumJSONRPC.ContractTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Contract
import Mox
describe "execute_contract_functions/3" do
test "executes the functions with and without the block_number, returns results in order" do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
abi = [
%{
"constant" => false,
"inputs" => [],
"name" => "get1",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get2",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get3",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
contract_address = "0x0000000000000000000000000000000000000000"
functions = [
%{contract_address: contract_address, function_name: "get1", args: []},
%{contract_address: contract_address, function_name: "get2", args: [], block_number: 1000},
%{contract_address: contract_address, function_name: "get3", args: []}
]
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn requests, _options ->
{:ok,
requests
|> Enum.map(fn
%{id: id, method: "eth_call", params: [%{data: "0x054c1a75", to: ^contract_address}, "latest"]} ->
%{
id: id,
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
}
%{id: id, method: "eth_call", params: [%{data: "0xd2178b08", to: ^contract_address}, "0x3E8"]} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000034"
}
%{id: id, method: "eth_call", params: [%{data: "0x8321045c", to: ^contract_address}, "latest"]} ->
%{
id: id,
error: %{code: -32015, data: "something", message: "Some error"}
}
end)
|> Enum.shuffle()}
end
)
blockchain_result = [
{:ok, [42]},
{:ok, [52]},
{:error, "(-32015) Some error"}
]
assert EthereumJSONRPC.execute_contract_functions(
functions,
abi,
json_rpc_named_arguments
) == blockchain_result
end
test "returns errors if JSONRPC request fails" do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
abi = [
%{
"constant" => false,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
}
]
contract_address = "0x0000000000000000000000000000000000000000"
functions = [
%{contract_address: contract_address, function_name: "get", args: []},
%{contract_address: contract_address, function_name: "get", args: [], block_number: 1000}
]
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn _requests, _options ->
{:error, "Some error"}
end
)
blockchain_result = [
{:error, "Some error"},
{:error, "Some error"}
]
assert EthereumJSONRPC.execute_contract_functions(
functions,
abi,
json_rpc_named_arguments
) == blockchain_result
end
end
end

@ -13,7 +13,7 @@ defmodule EthereumJSONRPC.EncoderTest do
types: []
}
assert Encoder.encode_function_call({function_selector, []}) == {"get", "0x6d4ce63c"}
assert Encoder.encode_function_call(function_selector, []) == "0x6d4ce63c"
end
test "generates the correct encoding with arguments" do
@ -23,8 +23,8 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}]
}
assert Encoder.encode_function_call({function_selector, [10]}) ==
{"get", "0x9507d39a000000000000000000000000000000000000000000000000000000000000000a"}
assert Encoder.encode_function_call(function_selector, [10]) ==
"0x9507d39a000000000000000000000000000000000000000000000000000000000000000a"
end
test "generates the correct encoding with addresses arguments" do
@ -36,127 +36,12 @@ defmodule EthereumJSONRPC.EncoderTest do
args = ["0xdab1c67232f92b7707f49c08047b96a4db7a9fc6", "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493"]
assert Encoder.encode_function_call({function_selector, args}) ==
{"tokens",
"0x508493bc000000000000000000000000dab1c67232f92b7707f49c08047b96a4db7a9fc60000000000000000000000006937cb25eb54bc013b9c13c47ab38eb63edd1493"}
assert Encoder.encode_function_call(function_selector, args) ==
"0x508493bc000000000000000000000000dab1c67232f92b7707f49c08047b96a4db7a9fc60000000000000000000000006937cb25eb54bc013b9c13c47ab38eb63edd1493"
end
end
describe "get_selectors/2" do
test "return the selectors of the desired functions with their arguments" do
abi = [
%ABI.FunctionSelector{
function: "fn1",
returns: {:uint, 256},
types: [uint: 256]
},
%ABI.FunctionSelector{
function: "fn2",
returns: {:uint, 256},
types: [uint: 256]
}
]
fn1 = %ABI.FunctionSelector{
function: "fn1",
returns: {:uint, 256},
types: [uint: 256]
}
assert Encoder.get_selectors(abi, %{"fn1" => [10]}) == [{fn1, [10]}]
end
end
describe "get_selector_from_name/2" do
test "return the selector of the desired function" do
abi = [
%ABI.FunctionSelector{
function: "fn1",
returns: {:uint, 256},
types: [uint: 256]
},
%ABI.FunctionSelector{
function: "fn2",
returns: {:uint, 256},
types: [uint: 256]
}
]
fn1 = %ABI.FunctionSelector{
function: "fn1",
returns: {:uint, 256},
types: [uint: 256]
}
assert Encoder.get_selector_from_name(abi, "fn1") == fn1
end
end
describe "decode_abi_results/3" do
test "separates the selectors and map the results" do
result = [
%{
id: "get1",
jsonrpc: "2.0",
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
},
%{
id: "get2",
jsonrpc: "2.0",
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
},
%{
id: "get3",
jsonrpc: "2.0",
result: "0x0000000000000000000000000000000000000000000000000000000000000020"
}
]
abi = [
%{
"constant" => false,
"inputs" => [],
"name" => "get1",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get2",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get3",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
functions = %{
"get1" => [],
"get2" => [],
"get3" => []
}
assert Encoder.decode_abi_results(result, abi, functions) == %{
"get1" => {:ok, [42]},
"get2" => {:ok, [42]},
"get3" => {:ok, [32]}
}
end
end
describe "decode_result/1" do
describe "decode_result/2" do
test "correctly decodes the blockchain result" do
result = %{
id: "sum",
@ -170,7 +55,7 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}]
}
assert Encoder.decode_result({result, selector}) == {"sum", {:ok, [42]}}
assert Encoder.decode_result(result, selector) == {"sum", {:ok, [42]}}
end
test "correctly handles the blockchain error response" do
@ -189,7 +74,7 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}]
}
assert Encoder.decode_result({result, selector}) ==
assert Encoder.decode_result(result, selector) ==
{"sum", {:error, "(-32602) Invalid params: Invalid hex: Invalid character 'x' at position 134."}}
end
@ -199,7 +84,7 @@ defmodule EthereumJSONRPC.EncoderTest do
selector = %ABI.FunctionSelector{function: "name", types: [], returns: [:string]}
assert Encoder.decode_result({%{id: "storedName", result: result}, selector}) == {"storedName", {:ok, ["AION"]}}
assert Encoder.decode_result(%{id: "storedName", result: result}, selector) == {"storedName", {:ok, ["AION"]}}
end
end
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save