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

@ -1,3 +1,6 @@
:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0 :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 erlang 21.0.4
nodejs 10.11.0 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'`) 4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`) 5. Push to the branch (`git push origin my-new-feature`)
6. Create a new Pull Request 6. Create a new Pull Request
7. Update CHANGELOG.md with the link to PR and description of the changes
### General ### General

@ -18,3 +18,20 @@
## Upgrading ## 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".* *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) * [Goerli Testnet](https://blockscout.com/eth/goerli)
* [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) * [Rinkeby Testnet](https://blockscout.com/eth/rinkeby)
* [Ethereum Classic](https://blockscout.com/etc/mainnet) * [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 #### 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/) * [Fuse Network](https://explorer.fuse.io/)
* [ARTIS](https://explorer.sigma1.artis.network) * [ARTIS](https://explorer.sigma1.artis.network)
* [SafeChain](https://explorer.safechain.io) * [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/)
* [PIRL](http://pirl.es/)
* [Petrichor](https://explorer.petrichor-dev.com/)
### Visual Interface ### Visual Interface
@ -77,7 +84,7 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel
| Dependency | Mac | Linux | | 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) | | [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) | | [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) | | [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) | | [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 -` `cd apps/explorer && npm install; cd -`
7. Update your JSON RPC Variant in `apps/explorer/config/dev.exs` and `apps/indexer/config/dev.exs`. 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/` 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` 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; 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 { .pre-scrollable-shorty {
max-height: $pre-scrollable-max-height / 7; max-height: $pre-scrollable-max-height / 7;
} }

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

@ -19,7 +19,15 @@ function tryUpdateAge (el) {
if (timestamp.isValid()) updateAge(el, timestamp) if (timestamp.isValid()) updateAge(el, timestamp)
} }
function 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 if (fromNow !== el.innerHTML) el.innerHTML = fromNow
} }
updateAllAges() 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, config :block_scout_web,
namespace: BlockScoutWeb, namespace: BlockScoutWeb,
ecto_repos: [Explorer.Repo], 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, config :block_scout_web, BlockScoutWeb.Chain,
network: System.get_env("NETWORK"), network: System.get_env("NETWORK"),
@ -66,6 +68,18 @@ config :block_scout_web,
%{ %{
title: "Ethereum Classic", title: "Ethereum Classic",
url: "https://blockscout.com/etc/mainnet" 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, config :block_scout_web, BlockScoutWeb.SocialMedia,
twitter: "PoaNetwork", twitter: "PoaNetwork",
telegram: "oraclesnetwork", telegram: "poa_network",
facebook: "PoaNetwork", facebook: "PoaNetwork",
instagram: "PoaNetwork" instagram: "PoaNetwork"

@ -16,7 +16,7 @@ config :logger, :block_scout_web,
path: Path.absname("logs/test/block_scout_web.log") path: Path.absname("logs/test/block_scout_web.log")
# Configure wallaby # 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 config :explorer, Explorer.ExchangeRates, enabled: false, store: :none

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

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

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

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

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

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.Chain.SmartContract 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 def new(conn, %{"address_id" => address_hash_string}) do
changeset = changeset =
@ -13,7 +13,11 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
{:ok, compiler_versions} = CompilerVersion.fetch_versions() {: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 create( def create(
@ -21,17 +25,35 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
%{ %{
"address_id" => address_hash_string, "address_id" => address_hash_string,
"smart_contract" => smart_contract, "smart_contract" => smart_contract,
"external_libraries" => external_libraries "external_libraries" => external_libraries,
"evm_version" => evm_version,
"optimization" => optimization
} }
) do ) 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} -> {:ok, _smart_contract} ->
redirect(conn, to: address_contract_path(conn, :index, address_hash_string)) redirect(conn, to: address_contract_path(conn, :index, address_hash_string))
{:error, changeset} -> {:error, changeset} ->
{:ok, compiler_versions} = CompilerVersion.fetch_versions() {: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 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 BlockScoutWeb.InternalTransactionView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.CoinBalance.OnDemandFetcher alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@ -67,7 +67,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
conn, conn,
"index.html", "index.html",
address: address, address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn), current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"], filter: params["filter"],

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

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

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

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

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressValidationController do
alias BlockScoutWeb.BlockView alias BlockScoutWeb.BlockView
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Indexer.CoinBalance.OnDemandFetcher alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
@ -76,7 +76,7 @@ defmodule BlockScoutWeb.AddressValidationController do
conn, conn,
"index.html", "index.html",
address: address, address: address,
coin_balance_status: OnDemandFetcher.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn), current_path: current_path(conn),
transaction_count: transaction_count(address), transaction_count: transaction_count(address),
validation_count: validation_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 defmodule BlockScoutWeb.API.RPC.AddressController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.{Chain, Etherscan} alias Explorer.{Chain, Etherscan}
alias Explorer.Chain.{Address, Wei} 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 def balance(conn, params, template \\ :balance) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do
@ -148,7 +163,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end end
def getminedblocks(conn, params) do 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), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param), {:format, {:ok, address_hash}} <- to_address_hash(address_param),
@ -174,7 +189,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
def optional_params(params) do def optional_params(params) do
%{} %{}
|> put_order_by_direction(params) |> put_order_by_direction(params)
|> put_pagination_options(params) |> Helpers.put_pagination_options(params)
|> put_start_block(params) |> put_start_block(params)
|> put_end_block(params) |> put_end_block(params)
|> put_filter_by(params) |> put_filter_by(params)
@ -260,6 +275,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
Enum.any?(address_hashes, &(&1 == :error)) Enum.any?(address_hashes, &(&1 == :error))
end 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 defp hashes_to_addresses(address_hashes) do
address_hashes address_hashes
|> Chain.hashes_to_addresses() |> Chain.hashes_to_addresses()
@ -317,24 +339,6 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end end
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 defp put_start_block(options, params) do
with %{"startblock" => startblock_param} <- params, with %{"startblock" => startblock_param} <- params,
{start_block, ""} <- Integer.parse(startblock_param) do {start_block, ""} <- Integer.parse(startblock_param) do

@ -1,7 +1,48 @@
defmodule BlockScoutWeb.API.RPC.ContractController do defmodule BlockScoutWeb.API.RPC.ContractController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.Chain 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 def getabi(conn, params) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), 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), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param), {:format, {:ok, address_hash}} <- to_address_hash(address_param),
{:contract, {:ok, contract}} <- to_smart_contract(address_hash) do {:contract, {:ok, contract}} <- to_smart_contract(address_hash) do
render(conn, :getsourcecode, %{contract: contract}) render(conn, :getsourcecode, %{
contract: contract,
address_hash: address_hash
})
else else
{:address_param, :error} -> {:address_param, :error} ->
render(conn, :error, error: "Query parameter address is required") render(conn, :error, error: "Query parameter address is required")
@ -33,8 +77,80 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
render(conn, :error, error: "Invalid address hash") render(conn, :error, error: "Invalid address hash")
{:contract, :not_found} -> {: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 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 end
defp fetch_address(params) do defp fetch_address(params) do
@ -48,10 +164,80 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
defp to_smart_contract(address_hash) do defp to_smart_contract(address_hash) do
result = result =
case Chain.address_hash_to_smart_contract(address_hash) do case Chain.address_hash_to_smart_contract(address_hash) do
nil -> :not_found nil ->
contract -> {:ok, contract} :not_found
contract ->
{:ok, SmartContract.preload_decompiled_smart_contract(contract)}
end end
{:contract, result} {:contract, result}
end 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 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 Plug.Conn
import Phoenix.Controller, only: [put_view: 2] 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 {:ok, conn} <- call_controller(conn, controller, action) do
conn conn
else else
_ -> {:error, :no_action} ->
conn conn
|> put_status(400) |> put_status(400)
|> put_view(RPCView) |> put_view(RPCView)
|> Controller.render(:error, error: "Unknown action") |> Controller.render(:error, error: "Unknown action")
|> halt() |> 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
end end
@ -46,26 +64,35 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
end end
@doc false @doc false
@spec translate_module(map(), String.t()) :: {:ok, module()} | :error @spec translate_module(map(), String.t()) :: {:ok, module()} | {:error, :no_action}
def translate_module(translations, module) do defp translate_module(translations, module) do
module_lowercase = String.downcase(module) 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 end
@doc false @doc false
@spec translate_action(String.t()) :: {:ok, atom()} | :error @spec translate_action(String.t()) :: {:ok, atom()} | {:error, :no_action}
def translate_action(action) do defp translate_action(action) do
action_lowercase = String.downcase(action) action_lowercase = String.downcase(action)
{:ok, String.to_existing_atom(action_lowercase)} {:ok, String.to_existing_atom(action_lowercase)}
rescue rescue
ArgumentError -> :error ArgumentError -> {:error, :no_action}
end end
@doc false @doc false
@spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | :error @spec call_controller(Conn.t(), module(), atom()) :: {:ok, Conn.t()} | {:error, :no_action} | {:error, Exception.t()}
def call_controller(conn, controller, action) do defp call_controller(conn, controller, action) do
if :erlang.function_exported(controller, action, 2) do
{:ok, controller.call(conn, action)} {:ok, controller.call(conn, action)}
else
{:error, :no_action}
end
rescue rescue
Conn.WrapperError -> :error e ->
{:error, Exception.format(:error, e, __STACKTRACE__)}
end end
end end

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

@ -13,8 +13,8 @@ defmodule BlockScoutWeb.CSPHeader do
"content-security-policy" => "\ "content-security-policy" => "\
connect-src 'self' #{websocket_endpoints(conn)}; \ connect-src 'self' #{websocket_endpoints(conn)}; \
default-src 'self';\ default-src 'self';\
script-src 'self' 'unsafe-inline' 'unsafe-eval';\ script-src 'self' 'unsafe-inline' 'unsafe-eval' https://nico-amsterdam.github.io;\
style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://nico-amsterdam.github.io;\
img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\ img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\
font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com 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 %{ @account_getminedblocks_example_value_error %{
"status" => "0", "status" => "0",
"message" => "No blocks found", "message" => "No blocks found",
@ -265,6 +276,45 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "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 %{ @contract_getabi_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -278,6 +328,49 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "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 %{ @contract_getsourcecode_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -720,9 +813,18 @@ defmodule BlockScoutWeb.Etherscan do
} }
} }
@account_model %{
name: "Account",
fields: %{
"address" => @address_hash_type,
"balance" => @wei_type
}
}
@contract_model %{ @contract_model %{
name: "Contract", name: "Contract",
fields: %{ fields: %{
"Address" => @address_hash_type,
"SourceCode" => %{ "SourceCode" => %{
type: "contract source code", type: "contract source code",
definition: "The contract's 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" => %{ "ABI" => %{
type: "ABI", type: "ABI",
definition: "JSON string for the contract's Application Binary Interface (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 %{ @logs_getlogs_action %{
name: "getLogs", name: "getLogs",
description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.", 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 %{ @contract_getabi_action %{
name: "getabi", name: "getabi",
description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", 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." 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: [ responses: [
%{ %{
code: "200", code: "200",
@ -1767,7 +2111,8 @@ defmodule BlockScoutWeb.Etherscan do
@account_tokentx_action, @account_tokentx_action,
@account_tokenbalance_action, @account_tokenbalance_action,
@account_tokenlist_action, @account_tokenlist_action,
@account_getminedblocks_action @account_getminedblocks_action,
@account_listaccounts_action
] ]
} }
@ -1798,8 +2143,10 @@ defmodule BlockScoutWeb.Etherscan do
@contract_module %{ @contract_module %{
name: "contract", name: "contract",
actions: [ actions: [
@contract_listcontracts_action,
@contract_getabi_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 defp broadcast_block(block) do
preloaded_block = Repo.preload(block, [[miner: :names], :transactions, :rewards]) 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", %{ Endpoint.broadcast("blocks:new_block", "new_block", %{
block: preloaded_block, block: preloaded_block,

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

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

@ -59,6 +59,17 @@
</li> </li>
<% end %> <% 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 %> <%= if smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item"> <li class="nav-item">
<%= link( <%= link(
@ -115,6 +126,15 @@
<%= if smart_contract_verified?(@address) do %> <%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i> <i class="far fa-check-circle"></i>
<% end %> <% 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 %>
<% end %> <% end %>
<%= if smart_contract_with_read_only_functions?(@address) do %> <%= if smart_contract_with_read_only_functions?(@address) do %>

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

@ -27,6 +27,11 @@
<%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger" %> <%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger" %>
</div> </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"> <div class="form-group mb-4">
<%= label f, "Optimization" %> <%= label f, "Optimization" %>
@ -43,6 +48,11 @@
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %> <%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %>
</div> </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"> <div class="form-group mb-4">
<%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code below") %> <%= 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" %> <%= textarea f, :contract_source_code, class: "form-control monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %>
@ -50,7 +60,7 @@
</div> </div>
<div class="form-group mb-4"> <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" %> <%= 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" %> <%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger", "data-test": "contract-constructor-arguments-error" %>
</div> </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) to: block_path(BlockScoutWeb.Endpoint, :show, @internal_transaction.block_number)
) %> ) %>
</span> </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 %> <%= if assigns[:current_address] do %>
<span class="mr-2 mr-md-0 order-0 order-md-3"> <span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %> <%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %>

@ -11,13 +11,13 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="icon-links icon-links-primary footer-social-icons"> <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> <i class="fab fa-github"></i>
</a> </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> <i class="fab fa-twitter"></i>
</a> </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> <i class="fab fa-telegram-plane"></i>
</a> </a>
</div> </div>
@ -90,8 +90,9 @@
<% version = version() %> <% version = version() %>
<%= unless ignore_version?(version) do %> <%= unless ignore_version?(version) do %>
<% release_link = release_link(version) %>
<div> <div>
<%= gettext("Version") %>: <%= version %> <%= gettext("Version") %>: <%= release_link %>
</div> </div>
<% end %> <% end %>
</div> </div>

@ -79,8 +79,24 @@
</ul> </ul>
<div class="search-form d-lg-flex d-inline-block"> <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 -> %> <%= 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"> <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" %> <%= 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"> <div class="input-group-append">
<button class="input-group-text" id="search-icon"> <button class="input-group-text" id="search-icon">
<%= render BlockScoutWeb.IconsView, "_search_icon.html" %> <%= render BlockScoutWeb.IconsView, "_search_icon.html" %>

@ -16,6 +16,21 @@
<meta name="msapplication-config" content="<%= static_path(@conn, "/browserconfig.xml") %>"> <meta name="msapplication-config" content="<%= static_path(@conn, "/browserconfig.xml") %>">
<meta name="theme-color" content="#ffffff"> <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") %> <%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %>
</head> </head>

@ -35,7 +35,7 @@
<span class="mr-2 mr-md-0 order-1"> <span class="mr-2 mr-md-0 order-1">
<%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %> <%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %>
</span> </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 %> <%= if from_or_to_address?(@transaction, assigns[:current_address]) do %>
<span class="mr-2 mr-md-0 order-0 order-md-3"> <span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if @transaction.from_address_hash == assigns[:current_address].hash do %> <%= 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> <dt class="col-sm-3 text-muted"> <%= gettext "TX Fee" %> </dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<%= formatted_fee(@transaction, denomination: :ether) %> <%= formatted_fee(@transaction, denomination: :ether) %>
(<span data-wei-value=<%= fee(@transaction) %>
<% if !empty_exchange_rate?(@exchange_rate) do %> <%= if !empty_exchange_rate?(@exchange_rate) do %>
data-usd-exchange-rate=<%= @exchange_rate.usd_value %> (<span data-wei-value=<%= fee(@transaction) %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>)
<% end %> <% end %>
></span>)
</dd> </dd>
</dl> </dl>
<!-- Processing Time --> <!-- Processing Time -->
@ -139,6 +138,42 @@
</div> </div>
</div> </div>
<%= 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>
<% 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"> <div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value --> <!-- Value -->
<div class="card card-primary flex-grow-1"> <div class="card card-primary flex-grow-1">
@ -152,7 +187,7 @@
</div> </div>
</div> </div>
</div> </div>
<% end %>
<!-- Gas --> <!-- Gas -->
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0"> <div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0">
<div class="card-body"> <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 defmodule BlockScoutWeb.AddressView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
require Logger
import BlockScoutWeb.AddressController, only: [validation_count: 1] import BlockScoutWeb.AddressController, only: [validation_count: 1]
alias BlockScoutWeb.LayoutView alias BlockScoutWeb.LayoutView
@ -14,6 +16,7 @@ defmodule BlockScoutWeb.AddressView do
@tabs [ @tabs [
"coin_balances", "coin_balances",
"contracts", "contracts",
"decompiled_contracts",
"internal_transactions", "internal_transactions",
"read_contract", "read_contract",
"tokens", "tokens",
@ -203,6 +206,11 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false 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 def token_title(%Token{name: nil, contract_address_hash: contract_address_hash}) do
contract_address_hash contract_address_hash
|> to_string |> to_string
@ -211,6 +219,12 @@ defmodule BlockScoutWeb.AddressView do
def token_title(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})" 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 def trimmed_hash(%Hash{} = hash) do
string_hash = to_string(hash) string_hash = to_string(hash)
"#{String.slice(string_hash, 0..5)}#{String.slice(string_hash, -6..-1)}" "#{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 address.contracts_creation_transaction.from_address_hash
end 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 defp matching_address_check(%Address{hash: hash} = current_address, %Address{hash: hash}, contract?, truncate) do
[ [
view_module: __MODULE__, view_module: __MODULE__,
@ -274,6 +294,7 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["transactions"]), do: gettext("Transactions") defp tab_name(["transactions"]), do: gettext("Transactions")
defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions") defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions")
defp tab_name(["contracts"]), do: gettext("Code") 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(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History") defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated") defp tab_name(["validations"]), do: gettext("Blocks Validated")

@ -3,8 +3,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
alias BlockScoutWeb.API.RPC.RPCView 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 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 end
def render("balance.json", assigns) do def render("balance.json", assigns) do
@ -16,7 +21,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
Enum.map(addresses, fn address -> Enum.map(addresses, fn address ->
%{ %{
"account" => "#{address.hash}", "account" => "#{address.hash}",
"balance" => "#{address.fetched_coin_balance.value}" "balance" => balance(address)
} }
end) end)
@ -56,6 +61,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end 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 defp prepare_transaction(transaction) do
%{ %{
"blockNumber" => "#{transaction.block_number}", "blockNumber" => "#{transaction.block_number}",
@ -145,4 +157,8 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"symbol" => token.symbol "symbol" => token.symbol
} }
end end
defp balance(address) do
address.fetched_coin_balance && address.fetched_coin_balance.value && "#{address.fetched_coin_balance.value}"
end
end end

@ -2,40 +2,108 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView 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 def render("getabi.json", %{abi: abi}) do
RPCView.render("show.json", data: Jason.encode!(abi)) RPCView.render("show.json", data: Jason.encode!(abi))
end end
def render("getsourcecode.json", %{contract: contract}) do def render("getsourcecode.json", %{contract: contract, address_hash: address_hash}) do
RPCView.render("show.json", data: prepare_contract(contract)) RPCView.render("show.json", data: [prepare_source_code_contract(contract, address_hash)])
end end
def render("error.json", assigns) do def render("error.json", assigns) do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end
defp prepare_contract(nil) do 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" => "", "SourceCode" => "",
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"ContractName" => "", "ContractName" => "",
"CompilerVersion" => "", "CompilerVersion" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => "",
"OptimizationUsed" => "" "OptimizationUsed" => ""
} }
]
end end
defp prepare_contract(contract) do 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, "SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi), "ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name, "ContractName" => contract.name,
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => contract.compiler_version, "CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0") "OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
} }
]
end 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 end

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

@ -76,6 +76,16 @@ defmodule BlockScoutWeb.LayoutView do
BlockScoutWeb.version() BlockScoutWeb.version()
end 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?("unknown"), do: true
def ignore_version?(_), do: false def ignore_version?(_), do: false

@ -1,15 +1,17 @@
defmodule BlockScoutWeb.TransactionView do defmodule BlockScoutWeb.TransactionView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias ABI.TypeDecoder
alias BlockScoutWeb.{AddressView, BlockView, TabHelpers} alias BlockScoutWeb.{AddressView, BlockView, TabHelpers}
alias Cldr.Number alias Cldr.Number
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Block.Reward 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 Explorer.ExchangeRates.Token
alias Timex.Duration alias Timex.Duration
import BlockScoutWeb.Gettext import BlockScoutWeb.Gettext
import BlockScoutWeb.Tokens.Helpers
@tabs ["token_transfers", "internal_transactions", "logs"] @tabs ["token_transfers", "internal_transactions", "logs"]
@ -31,6 +33,85 @@ defmodule BlockScoutWeb.TransactionView do
def value_transfer?(_), do: false 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 def processing_time_duration(%Transaction{block: nil}) do
:pending :pending
end end
@ -257,4 +338,10 @@ defmodule BlockScoutWeb.TransactionView do
defp tab_name(["token_transfers"]), do: gettext("Token Transfers") defp tab_name(["token_transfers"]), do: gettext("Token Transfers")
defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions") defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions")
defp tab_name(["logs"]), do: gettext("Logs") defp tab_name(["logs"]), do: gettext("Logs")
defp decode_params(params, types) do
params
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)
end
end end

@ -15,7 +15,7 @@ defmodule BlockScoutWeb.Mixfile do
plt_add_deps: :transitive, plt_add_deps: :transitive,
ignore_warnings: "../../.dialyzer-ignore" ignore_warnings: "../../.dialyzer-ignore"
], ],
elixir: "~> 1.6", elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock", lockfile: "../../mix.lock",
package: package(), package: package(),
@ -122,13 +122,14 @@ defmodule BlockScoutWeb.Mixfile do
# Tracing # Tracing
{:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true}, {:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true},
# `:spandex` integration with Datadog # `:spandex` integration with Datadog
{:spandex_datadog, "~> 0.3.1"}, {:spandex_datadog, "~> 0.4.0"},
# `:spandex` tracing of `:phoenix` # `:spandex` tracing of `:phoenix`
{:spandex_phoenix, "~> 0.3.1"}, {:spandex_phoenix, "~> 0.3.1"},
{:timex, "~> 3.4"}, {: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 # `: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 end

@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -62,7 +62,7 @@ msgid "(query)"
msgstr "" msgstr ""
#, elixir-format #, 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." msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr "" msgstr ""
@ -99,7 +99,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 #: 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/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" msgid "Address"
msgstr "" msgstr ""
@ -155,7 +155,7 @@ msgid "Block Height: %{height}"
msgstr "" msgstr ""
#, elixir-format #, 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..." msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
@ -165,7 +165,7 @@ msgid "Block Number"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:20 #: lib/block_scout_web/views/transaction_view.ex:22
msgid "Block Pending" msgid "Block Pending"
msgstr "" msgstr ""
@ -182,22 +182,22 @@ msgid "Blocks"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Blocks Indexed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40 #: 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/_tabs.html.eex:114
#: lib/block_scout_web/templates/address/overview.html.eex:51 #: 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:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57 #: 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" msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, 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 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: 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:103
#: lib/block_scout_web/templates/address/overview.html.eex:97 #: 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:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close" msgid "Close"
@ -218,11 +218,11 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53 #: 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/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:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 #: 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" msgid "Code"
msgstr "" msgstr ""
@ -266,24 +266,24 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:13 #: 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" msgid "Contract Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 #: 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:39
#: lib/block_scout_web/views/address_view.ex:70 #: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending" msgid "Contract Address Pending"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:205 #: lib/block_scout_web/views/transaction_view.ex:286
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:204 #: lib/block_scout_web/views/transaction_view.ex:285
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Created by"
msgstr "" msgstr ""
@ -367,32 +367,32 @@ msgid "ETH"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Enter the Solidity Contract Code below"
msgstr "" msgstr ""
#, elixir-format #, 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." msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:132 #: lib/block_scout_web/views/transaction_view.ex:213
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
#, elixir-format #, 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/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/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27 #: 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 #: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -408,7 +408,7 @@ msgid "Execute"
msgstr "" msgstr ""
#, elixir-format #, 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..." msgid "Fetching tokens..."
msgstr "" msgstr ""
@ -431,7 +431,7 @@ msgid "GET"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:159 #: lib/block_scout_web/templates/transaction/overview.html.eex:194
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -472,7 +472,7 @@ msgid "IN"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Indexing Tokens"
msgstr "" msgstr ""
@ -488,14 +488,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: 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_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24 #: 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:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: 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/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:275 #: lib/block_scout_web/views/address_view.ex:295
#: lib/block_scout_web/views/transaction_view.ex:258 #: lib/block_scout_web/views/transaction_view.ex:339
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -508,12 +508,12 @@ msgid "Inventory"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Less than"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:171 #: lib/block_scout_web/templates/transaction/overview.html.eex:206
msgid "Limit" msgid "Limit"
msgstr "" 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:21
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:48 #: 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/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" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28 #: lib/block_scout_web/templates/chain/show.html.eex:28
#: lib/block_scout_web/templates/layout/app.html.eex:49 #: lib/block_scout_web/templates/layout/app.html.eex:64
#: lib/block_scout_web/views/address_view.ex:118 #: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:113 #: lib/block_scout_web/views/transaction_view.ex:194
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
@ -603,7 +603,7 @@ msgid "Next Page"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "No"
msgstr "" msgstr ""
@ -661,8 +661,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #: 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:208
#: lib/block_scout_web/views/transaction_view.ex:161 #: lib/block_scout_web/views/transaction_view.ex:242
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -679,13 +679,13 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:21 #: 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" msgid "Price"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13 #: 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:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -698,11 +698,11 @@ msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:65 #: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/templates/address/_tabs.html.eex:122 #: 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:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 #: 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 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Reset"
msgstr "" msgstr ""
@ -728,16 +728,11 @@ msgid "Responses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83 #: lib/block_scout_web/templates/layout/_topnav.html.eex:89
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90 #: lib/block_scout_web/templates/layout/_topnav.html.eex:106
msgid "Search" msgid "Search"
msgstr "" 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 #, elixir-format
#: #:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28 #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -767,7 +762,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:129 #: lib/block_scout_web/views/transaction_view.ex:210
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -877,8 +872,10 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:4 #: 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/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" msgid "Token Transfer"
msgstr "" msgstr ""
@ -890,18 +887,18 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36 #: 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/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:35 #: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:257 #: lib/block_scout_web/views/transaction_view.ex:338
msgid "Token Transfers" msgid "Token Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: 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/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12 #: 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:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19 #: 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" msgid "Tokens"
msgstr "" msgstr ""
@ -931,7 +928,7 @@ msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:206 #: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -953,7 +950,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:5 #: 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_transaction/index.html.eex:53
#: lib/block_scout_web/templates/address_validation/index.html.eex:14 #: lib/block_scout_web/templates/address_validation/index.html.eex:14
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13 #: 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/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93 #: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 #: 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" msgid "Transactions"
msgstr "" msgstr ""
@ -998,7 +995,7 @@ msgid "Unique Token"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:164 #: lib/block_scout_web/templates/transaction/overview.html.eex:199
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1019,7 +1016,7 @@ msgid "Validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:146 #: lib/block_scout_web/templates/transaction/overview.html.eex:181
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1029,7 +1026,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1089,12 +1086,12 @@ msgid "Wei"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Yes"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:64 #: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at" msgid "at"
msgstr "" msgstr ""
@ -1170,7 +1167,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, 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...." msgid "Loading...."
msgstr "" msgstr ""
@ -1215,7 +1212,7 @@ msgid "This API is provided for developers transitioning their applications from
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Raw Input"
msgstr "" msgstr ""
@ -1404,8 +1401,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30 #: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/address/_tabs.html.eex:107
#: lib/block_scout_web/views/address_view.ex:278 #: lib/block_scout_web/views/address_view.ex:299
msgid "Coin Balance History" msgid "Coin Balance History"
msgstr "" msgstr ""
@ -1557,7 +1554,7 @@ msgid "Test Networks"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_footer.html.eex:94 #: lib/block_scout_web/templates/layout/_footer.html.eex:95
msgid "Version" msgid "Version"
msgstr "" msgstr ""
@ -1587,87 +1584,163 @@ msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "1 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "1 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "2 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "2 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "3 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "3 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "4 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "4 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "5 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "5 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Contract Libraries"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53 #: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
msgid "Last Balance Update: Block #" msgid "Last Balance Update: Block #"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transactions Sent"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transaction Speed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122 #: lib/block_scout_web/templates/transaction/overview.html.eex:121
#: lib/block_scout_web/templates/transaction/overview.html.eex:127 #: lib/block_scout_web/templates/transaction/overview.html.eex:126
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "UTF-8"
msgstr "" 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 "" msgstr ""
#, elixir-format #, 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)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -62,7 +62,7 @@ msgid "(query)"
msgstr "" msgstr ""
#, elixir-format #, 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." msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr "" msgstr ""
@ -99,7 +99,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 #: 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/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" msgid "Address"
msgstr "" msgstr ""
@ -155,7 +155,7 @@ msgid "Block Height: %{height}"
msgstr "" msgstr ""
#, elixir-format #, 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..." msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
@ -165,7 +165,7 @@ msgid "Block Number"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:20 #: lib/block_scout_web/views/transaction_view.ex:22
msgid "Block Pending" msgid "Block Pending"
msgstr "" msgstr ""
@ -182,22 +182,22 @@ msgid "Blocks"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Blocks Indexed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40 #: 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/_tabs.html.eex:114
#: lib/block_scout_web/templates/address/overview.html.eex:51 #: 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:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57 #: 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" msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, 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 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: 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:103
#: lib/block_scout_web/templates/address/overview.html.eex:97 #: 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:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close" msgid "Close"
@ -218,11 +218,11 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53 #: 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/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:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 #: 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" msgid "Code"
msgstr "" msgstr ""
@ -266,24 +266,24 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:13 #: 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" msgid "Contract Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 #: 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:39
#: lib/block_scout_web/views/address_view.ex:70 #: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending" msgid "Contract Address Pending"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:205 #: lib/block_scout_web/views/transaction_view.ex:286
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:204 #: lib/block_scout_web/views/transaction_view.ex:285
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Created by"
msgstr "" msgstr ""
@ -367,32 +367,32 @@ msgid "ETH"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Enter the Solidity Contract Code below"
msgstr "" msgstr ""
#, elixir-format #, 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." msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:132 #: lib/block_scout_web/views/transaction_view.ex:213
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, 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)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
#, elixir-format #, 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/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/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27 #: 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 #: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether" msgid "Ether"
msgstr "POA" msgstr "POA"
@ -408,7 +408,7 @@ msgid "Execute"
msgstr "" msgstr ""
#, elixir-format #, 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..." msgid "Fetching tokens..."
msgstr "" msgstr ""
@ -431,7 +431,7 @@ msgid "GET"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:159 #: lib/block_scout_web/templates/transaction/overview.html.eex:194
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -472,7 +472,7 @@ msgid "IN"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Indexing Tokens"
msgstr "" msgstr ""
@ -488,14 +488,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: 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_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24 #: 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:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: 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/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:275 #: lib/block_scout_web/views/address_view.ex:295
#: lib/block_scout_web/views/transaction_view.ex:258 #: lib/block_scout_web/views/transaction_view.ex:339
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -508,12 +508,12 @@ msgid "Inventory"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Less than"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:171 #: lib/block_scout_web/templates/transaction/overview.html.eex:206
msgid "Limit" msgid "Limit"
msgstr "" 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:21
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:48 #: 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/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" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28 #: lib/block_scout_web/templates/chain/show.html.eex:28
#: lib/block_scout_web/templates/layout/app.html.eex:49 #: lib/block_scout_web/templates/layout/app.html.eex:64
#: lib/block_scout_web/views/address_view.ex:118 #: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:113 #: lib/block_scout_web/views/transaction_view.ex:194
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
@ -603,7 +603,7 @@ msgid "Next Page"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "No"
msgstr "" msgstr ""
@ -661,8 +661,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #: 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:208
#: lib/block_scout_web/views/transaction_view.ex:161 #: lib/block_scout_web/views/transaction_view.ex:242
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -679,13 +679,13 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:21 #: 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" msgid "Price"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13 #: 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:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -698,11 +698,11 @@ msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:65 #: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/templates/address/_tabs.html.eex:122 #: 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:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 #: 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 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Reset"
msgstr "" msgstr ""
@ -728,16 +728,11 @@ msgid "Responses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83 #: lib/block_scout_web/templates/layout/_topnav.html.eex:89
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90 #: lib/block_scout_web/templates/layout/_topnav.html.eex:106
msgid "Search" msgid "Search"
msgstr "" 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 #, elixir-format
#: #:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28 #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -767,7 +762,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:129 #: lib/block_scout_web/views/transaction_view.ex:210
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -877,8 +872,10 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:4 #: 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/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" msgid "Token Transfer"
msgstr "" msgstr ""
@ -890,18 +887,18 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36 #: 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/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:35 #: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:257 #: lib/block_scout_web/views/transaction_view.ex:338
msgid "Token Transfers" msgid "Token Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: 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/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12 #: 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:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19 #: 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" msgid "Tokens"
msgstr "" msgstr ""
@ -931,7 +928,7 @@ msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:206 #: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -953,7 +950,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:5 #: 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_transaction/index.html.eex:53
#: lib/block_scout_web/templates/address_validation/index.html.eex:14 #: lib/block_scout_web/templates/address_validation/index.html.eex:14
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13 #: 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/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93 #: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 #: 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" msgid "Transactions"
msgstr "" msgstr ""
@ -998,7 +995,7 @@ msgid "Unique Token"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:164 #: lib/block_scout_web/templates/transaction/overview.html.eex:199
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1019,7 +1016,7 @@ msgid "Validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:146 #: lib/block_scout_web/templates/transaction/overview.html.eex:181
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1029,7 +1026,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1089,12 +1086,12 @@ msgid "Wei"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Yes"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:64 #: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at" msgid "at"
msgstr "" msgstr ""
@ -1170,7 +1167,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, 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...." msgid "Loading...."
msgstr "" msgstr ""
@ -1215,7 +1212,7 @@ msgid "This API is provided for developers transitioning their applications from
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Raw Input"
msgstr "" msgstr ""
@ -1404,8 +1401,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30 #: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/address/_tabs.html.eex:107
#: lib/block_scout_web/views/address_view.ex:278 #: lib/block_scout_web/views/address_view.ex:299
msgid "Coin Balance History" msgid "Coin Balance History"
msgstr "" msgstr ""
@ -1557,7 +1554,7 @@ msgid "Test Networks"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_footer.html.eex:94 #: lib/block_scout_web/templates/layout/_footer.html.eex:95
msgid "Version" msgid "Version"
msgstr "" msgstr ""
@ -1587,87 +1584,163 @@ msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "1 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "1 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "2 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "2 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "3 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "3 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "4 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "4 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "5 Library Address"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "5 Library Name"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Contract Libraries"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53 #: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
msgid "Last Balance Update: Block #" msgid "Last Balance Update: Block #"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transactions Sent"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transaction Speed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122 #: lib/block_scout_web/templates/transaction/overview.html.eex:121
#: lib/block_scout_web/templates/transaction/overview.html.eex:127 #: lib/block_scout_web/templates/transaction/overview.html.eex:126
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "UTF-8"
msgstr "" 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() assert {:ok, %Address{hash: ^hash}} = address |> Phoenix.Param.to_param() |> Chain.from_param()
end 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 test "returns {:error, :not_found} when garbage is passed in" do
assert {:error, :not_found} = Chain.from_param("any ol' thing") assert {:error, :not_found} = Chain.from_param("any ol' thing")
end end

@ -6,6 +6,49 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
alias Explorer.Chain.{Transaction, Wei} alias Explorer.Chain.{Transaction, Wei}
alias BlockScoutWeb.API.RPC.AddressController 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 describe "balance" do
test "with missing address hash", %{conn: conn} do test "with missing address hash", %{conn: conn} do
params = %{ params = %{
@ -724,34 +767,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
test "ignores pagination params if page is less than 1", %{conn: conn} do test "ignores offset param if offset 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
address = insert(:address) address = insert(:address)
6 6
@ -778,7 +794,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end 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) address = insert(:address)
6 6

@ -1,5 +1,244 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase 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 describe "getabi" do
test "with missing address hash", %{conn: conn} do test "with missing address hash", %{conn: conn} do
@ -119,11 +358,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [ expected_result = [
%{ %{
"Address" => "",
"SourceCode" => "", "SourceCode" => "",
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"ContractName" => "", "ContractName" => "",
"CompilerVersion" => "", "CompilerVersion" => "",
"OptimizationUsed" => "" "OptimizationUsed" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => ""
} }
] ]
@ -148,13 +390,16 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [ expected_result = [
%{ %{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code, "SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi), "ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name, "ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version, "CompilerVersion" => contract.compiler_version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
# The contract's optimization value is true, so the expected value # The contract's optimization value is true, so the expected value
# for `OptimizationUsed` is "1". If it was false, the expected value # for `OptimizationUsed` is "1". If it was false, the expected value
# would be "0". # would be "0".
"DecompilerVersion" => "",
"OptimizationUsed" => "1" "OptimizationUsed" => "1"
} }
] ]
@ -169,4 +414,100 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
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 end

@ -75,20 +75,4 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslatorTest do
assert json_response(result, 200) == %{} assert json_response(result, 200) == %{}
end end
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 end

@ -367,6 +367,59 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
refute response["result"] refute response["result"]
end 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 test "with a txhash with ok status", %{conn: conn} do
block = insert(:block) block = insert(:block)
@ -409,7 +462,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"data" => "#{log.data}", "data" => "#{log.data}",
"topics" => ["first topic", "second topic", nil, nil] "topics" => ["first topic", "second topic", nil, nil]
} }
] ],
"next_page_params" => nil
} }
assert response = 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
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 test "finds a consensus block by block number", %{conn: conn} do
insert(:block, number: 37) insert(:block, number: 37)
conn = get(conn, "/search?q=37") conn = get(conn, "/search?q=37")

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

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

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

@ -215,7 +215,7 @@ defmodule BlockScoutWeb.Schema.Query.AddressTest do
"r" => to_string(transaction.r), "r" => to_string(transaction.r),
"s" => to_string(transaction.s), "s" => to_string(transaction.s),
"status" => nil, "status" => nil,
"v" => transaction.v, "v" => to_string(transaction.v),
"value" => to_string(transaction.value.value), "value" => to_string(transaction.value.value),
"from_address_hash" => to_string(transaction.from_address_hash), "from_address_hash" => to_string(transaction.from_address_hash),
"to_address_hash" => to_string(transaction.to_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), "r" => to_string(transaction.r),
"s" => to_string(transaction.s), "s" => to_string(transaction.s),
"status" => transaction.status |> to_string() |> String.upcase(), "status" => transaction.status |> to_string() |> String.upcase(),
"v" => transaction.v, "v" => to_string(transaction.v),
"value" => to_string(transaction.value.value), "value" => to_string(transaction.value.value),
"from_address_hash" => to_string(transaction.from_address_hash), "from_address_hash" => to_string(transaction.from_address_hash),
"to_address_hash" => to_string(transaction.to_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" assert LayoutView.network_title() == "POA"
end end
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 end

@ -47,6 +47,30 @@ defmodule BlockScoutWeb.TransactionViewTest do
end end
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 describe "processing_time_duration/2" do
test "returns :pending if the transaction has no block" do test "returns :pending if the transaction has no block" do
transaction = build(:transaction, block: nil) 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), wait_per_timeout: :timer.seconds(20),
max_jitter: :timer.seconds(2) 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, config :ethereum_jsonrpc, EthereumJSONRPC.Tracer,
service: :ethereum_jsonrpc, service: :ethereum_jsonrpc,
adapter: SpandexDatadog.Adapter, adapter: SpandexDatadog.Adapter,

@ -7,7 +7,14 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
], ],
wait_per_timeout: 2, 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 config :ethereum_jsonrpc, EthereumJSONRPC.Tracer, disabled?: false

@ -28,6 +28,7 @@ defmodule EthereumJSONRPC do
alias EthereumJSONRPC.{ alias EthereumJSONRPC.{
Block, Block,
Blocks, Blocks,
Contract,
FetchedBalances, FetchedBalances,
FetchedBeneficiaries, FetchedBeneficiaries,
FetchedCodes, FetchedCodes,
@ -160,33 +161,9 @@ defmodule EthereumJSONRPC do
} }
]} ]}
""" """
@spec execute_contract_functions( @spec execute_contract_functions([Contract.call()], [map()], json_rpc_named_arguments) :: [Contract.call_result()]
[%{contract_address: String.t(), data: String.t(), id: String.t()}], def execute_contract_functions(functions, abi, json_rpc_named_arguments) do
json_rpc_named_arguments, Contract.execute_contract_functions(functions, abi, 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})
end end
@doc """ @doc """
@ -287,6 +264,16 @@ defmodule EthereumJSONRPC do
) )
end 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 """ @doc """
Fetches pending transactions from variant API. Fetches pending transactions from variant API.
""" """
@ -339,11 +326,18 @@ defmodule EthereumJSONRPC do
@doc """ @doc """
Converts `t:quantity/0` to `t:non_neg_integer/0`. 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 def quantity_to_integer("0x" <> hexadecimal_digits) do
String.to_integer(hexadecimal_digits, 16) String.to_integer(hexadecimal_digits, 16)
end end
def quantity_to_integer(string) do
case Integer.parse(string) do
{integer, ""} -> integer
_ -> :error
end
end
@doc """ @doc """
Converts `t:non_neg_integer/0` to `t:quantity/0` 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` Converts `t:timestamp/0` to `t:DateTime.t/0`
""" """
def timestamp_to_datetime(timestamp) do def timestamp_to_datetime(timestamp) do
timestamp case quantity_to_integer(timestamp) do
|> quantity_to_integer() :error ->
|> Timex.from_unix() nil
quantity ->
Timex.from_unix(quantity)
end
end end
defp fetch_blocks_by_params(params, request, json_rpc_named_arguments) defp fetch_blocks_by_params(params, request, json_rpc_named_arguments)

@ -9,16 +9,32 @@ defmodule EthereumJSONRPC.Application do
@impl Application @impl Application
def start(_type, _args) do def start(_type, _args) do
rolling_window_opts = config = Application.fetch_env!(:ethereum_jsonrpc, RequestCoordinator)
:ethereum_jsonrpc
|> Application.fetch_env!(RequestCoordinator)
|> Keyword.fetch!(:rolling_window_opts)
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), :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)
Supervisor.start_link(children, strategy: :one_for_one, name: EthereumJSONRPC.Supervisor) [child | children]
:error ->
raise "If you have configured `:throttle_rate_limit` you must also configure `:throttle_rolling_window_opts`"
end
else
children
end
end end
end end

@ -431,7 +431,8 @@ defmodule EthereumJSONRPC.Block do
Enum.into(block, %{}, &entry_to_elixir/1) Enum.into(block, %{}, &entry_to_elixir/1)
end 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)} {key, quantity_to_integer(quantity)}
end 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 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 """ @doc """
Given a function selector and a list of arguments, returns their encoded versions. Given a function selector and a list of arguments, returns their encoded versions.
This is what is expected on the Json RPC data parameter. This is what is expected on the Json RPC data parameter.
""" """
@spec encode_function_call({%ABI.FunctionSelector{}, [term()]}) :: {String.t(), String.t()} @spec encode_function_call(%ABI.FunctionSelector{}, [term()]) :: String.t()
def encode_function_call({function_selector, args}) do def encode_function_call(function_selector, args) do
encoded_args = encoded_args =
function_selector function_selector
|> ABI.encode(parse_args(args)) |> ABI.encode(parse_args(args))
|> Base.encode16(case: :lower) |> Base.encode16(case: :lower)
{function_selector.function, "0x" <> encoded_args} "0x" <> encoded_args
end end
defp parse_args(args) do defp parse_args(args) do
@ -62,39 +32,16 @@ defmodule EthereumJSONRPC.Encoder do
end) end)
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 """ @doc """
Given a result from the blockchain, and the function selector, returns the result decoded. 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}} {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}"}} {id, {:error, "(#{code}) #{message}"}}
end end
def decode_result({%{id: id, result: result}, function_selector}) do def decode_result(%{id: id, result: result}, function_selector) do
types_list = List.wrap(function_selector.returns) types_list = List.wrap(function_selector.returns)
decoded_data = decoded_data =

@ -21,6 +21,14 @@ defmodule EthereumJSONRPC.Ganache do
@impl EthereumJSONRPC.Variant @impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore 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 """ @doc """
Pending transaction fetching is not supported currently for Ganache. Pending transaction fetching is not supported currently for Ganache.

@ -32,6 +32,14 @@ defmodule EthereumJSONRPC.Geth do
end end
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 """ @doc """
Pending transaction fetching is not supported currently for Geth. 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]) chunked_json_rpc(tail, options, [decoded_body | decoded_response_bodies])
end end
{:error, :timeout} ->
rechunk_json_rpc(chunks, options, :timeout, decoded_response_bodies)
{:error, _} = error -> {:error, _} = error ->
error error
end end

@ -26,18 +26,26 @@ defmodule EthereumJSONRPC.Parity do
end end
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 """ @doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL.
""" """
@impl EthereumJSONRPC.Variant @impl EthereumJSONRPC.Variant
def fetch_internal_transactions(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
id_to_params = id_to_params(transactions_params) id_to_params = id_to_params(block_numbers)
with {:ok, responses} <- with {:ok, responses} <-
id_to_params id_to_params
|> trace_replay_transaction_requests() |> trace_replay_block_transactions_requests()
|> json_rpc(json_rpc_named_arguments) do |> 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
end end
@ -68,9 +76,9 @@ defmodule EthereumJSONRPC.Parity do
Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
end 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 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 = params =
traces traces
|> Traces.to_elixir() |> Traces.to_elixir()
@ -80,10 +88,10 @@ defmodule EthereumJSONRPC.Parity do
end end
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 when is_list(responses) and is_map(id_to_params) do
responses 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( |> Enum.reduce(
{:ok, []}, {:ok, []},
fn fn
@ -115,48 +123,48 @@ defmodule EthereumJSONRPC.Parity do
end end
end end
defp trace_replay_transaction_response_to_traces(%{id: id, result: %{"trace" => traces}}, id_to_params) defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
when is_list(traces) and is_map(id_to_params) do when is_list(results) and is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = block_number = Map.fetch!(id_to_params, id)
Map.fetch!(id_to_params, id)
annotated_traces = annotated_traces =
results
|> Stream.with_index()
|> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
traces traces
|> Stream.with_index() |> Stream.with_index()
|> Enum.map(fn {trace, index} -> |> Enum.map(fn {trace, index} ->
Map.merge(trace, %{ Map.merge(trace, %{
"blockNumber" => block_number, "blockNumber" => block_number,
"index" => index, "transactionHash" => transaction_hash,
"transactionIndex" => transaction_index, "transactionIndex" => transaction_index,
"transactionHash" => transaction_hash "index" => index
}) })
end) end)
end)
{:ok, annotated_traces} {:ok, annotated_traces}
end 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 when is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = block_number = Map.fetch!(id_to_params, id)
Map.fetch!(id_to_params, id)
annotated_error = annotated_error =
Map.put(error, :data, %{ Map.put(error, :data, %{
"blockNumber" => block_number, "blockNumber" => block_number
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
}) })
{:error, annotated_error} {:error, annotated_error}
end end
defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} -> Enum.map(id_to_params, fn {id, block_number} ->
trace_replay_transaction_request(%{id: id, hash_data: hash_data}) trace_replay_block_transactions_request(%{id: id, block_number: block_number})
end) end)
end end
defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]}) request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
end end
end end

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

@ -18,9 +18,14 @@ defmodule EthereumJSONRPC.RequestCoordinator do
the tracked window the tracked window
* `:max_jitter` - Maximimum amount of time in milliseconds to be added to each * `:max_jitter` - Maximimum amount of time in milliseconds to be added to each
wait before multiplied by timeout count 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 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: This is how the wait time for each request is calculated:
@ -33,13 +38,20 @@ defmodule EthereumJSONRPC.RequestCoordinator do
config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator, config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator,
rolling_window_opts: [ rolling_window_opts: [
window_count: 6, window_count: 6,
duration: :timer.seconds(10), duration: :timer.minutes(1),
table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter
], ],
wait_per_timeout: :timer.seconds(10), wait_per_timeout: :timer.seconds(10),
max_jitter: :timer.seconds(1) 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. 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 require EthereumJSONRPC.Tracer
@ -47,6 +59,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
alias EthereumJSONRPC.{RollingWindow, Tracer, Transport} alias EthereumJSONRPC.{RollingWindow, Tracer, Transport}
@error_key :throttleable_error_count @error_key :throttleable_error_count
@throttle_key :throttle_requests_count
@doc """ @doc """
Performs a JSON RPC request and adds necessary backoff. Performs a JSON RPC request and adds necessary backoff.
@ -64,12 +77,19 @@ defmodule EthereumJSONRPC.RequestCoordinator do
if sleep_time <= throttle_timeout do if sleep_time <= throttle_timeout do
:timer.sleep(sleep_time) :timer.sleep(sleep_time)
remaining_wait_time = throttle_timeout - sleep_time
case throttle_request(remaining_wait_time) do
:ok ->
trace_request(request, fn -> trace_request(request, fn ->
request request
|> transport.json_rpc(transport_options) |> transport.json_rpc(transport_options)
|> handle_transport_response() |> handle_transport_response()
end) end)
:error ->
{:error, :timeout}
end
else else
:timer.sleep(throttle_timeout) :timer.sleep(throttle_timeout)
@ -91,33 +111,78 @@ defmodule EthereumJSONRPC.RequestCoordinator do
defp handle_transport_response({:error, {:bad_gateway, _}} = error) do defp handle_transport_response({:error, {:bad_gateway, _}} = error) do
RollingWindow.inc(table(), @error_key) RollingWindow.inc(table(), @error_key)
inc_throttle_table()
error error
end end
defp handle_transport_response({:error, :timeout} = error) do defp handle_transport_response({:error, :timeout} = error) do
RollingWindow.inc(table(), @error_key) RollingWindow.inc(table(), @error_key)
inc_throttle_table()
error error
end 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 defp sleep_time do
wait_coefficient = RollingWindow.count(table(), @error_key) wait_coefficient = RollingWindow.count(table(), @error_key)
jitter = :rand.uniform(config(:max_jitter)) jitter = :rand.uniform(config!(:max_jitter))
wait_per_timeout = config(:wait_per_timeout) wait_per_timeout = config!(:wait_per_timeout)
wait_coefficient * (wait_per_timeout + jitter) wait_coefficient * (wait_per_timeout + jitter)
end end
defp table do defp table do
:rolling_window_opts :rolling_window_opts
|> config() |> config!()
|> Keyword.fetch!(:table) |> Keyword.fetch!(:table)
end 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 :ethereum_jsonrpc
|> Application.get_env(__MODULE__) |> Application.get_env(__MODULE__)
|> Keyword.fetch!(key) |> Keyword.fetch!(key)
end end
defp config(key) do
:ethereum_jsonrpc
|> Application.get_env(__MODULE__)
|> Keyword.get(key)
end
end end

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

@ -56,7 +56,8 @@ defmodule EthereumJSONRPC.Transactions do
to_address_hash: nil, to_address_hash: nil,
v: "0xbd", v: "0xbd",
value: 0, 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() EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore ) :: {: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 """ @doc """
Fetch the `t:Explorer.Chain.Transaction.changeset/2` params for pending transactions from the variant of the Ethereum Fetch the `t:Explorer.Chain.Transaction.changeset/2` params for pending transactions from the variant of the Ethereum
JSONRPC API. JSONRPC API.

@ -37,14 +37,14 @@ defmodule EthereumJSONRPC.WebSocket do
@doc """ @doc """
Allow `c:start_link/1` to be called as part of a supervision tree. 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 """ @doc """
Starts web socket attached to `url` with `options`. Starts web socket attached to `url` with `options`.
""" """
# Return is same as `t:GenServer.on_start/0` # Return is same as `t:GenServer.on_start/0`
@callback start_link([url :: String.t() | options :: term()]) :: @callback start_link([(url :: String.t()) | (options :: term())]) ::
{:ok, pid()} | :ignore | {:error, {:already_started, pid()} | reason :: term()} {:ok, pid()} | :ignore | {:error, {:already_started, pid()} | (reason :: term())}
@doc """ @doc """
Run a single Remote Procedure Call (RPC) `t:EthereumJSONRPC.Transport.request/0` through `t:web_socket/0`. 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 @impl WebSocket
# only allow secure WSS # 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 = fsm_name =
case Keyword.fetch(gen_fsm_options, :name) do case Keyword.fetch(gen_fsm_options, :name) do
{:ok, name} when is_atom(name) -> {:local, name} {:ok, name} when is_atom(name) -> {:local, name}
@ -68,6 +70,7 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
__MODULE__, __MODULE__,
url, url,
ssl_verify: :verify_peer, ssl_verify: :verify_peer,
keepalive: keepalive,
socket_opts: [ socket_opts: [
cacerts: :certifi.cacerts(), cacerts: :certifi.cacerts(),
depth: 99, depth: 99,
@ -78,7 +81,9 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
) )
end 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 = fsm_name =
case Keyword.fetch(gen_fsm_options, :name) do case Keyword.fetch(gen_fsm_options, :name) do
{:ok, name} when is_atom(name) -> {:local, name} {:ok, name} when is_atom(name) -> {:local, name}
@ -90,7 +95,7 @@ defmodule EthereumJSONRPC.WebSocket.WebSocketClient do
url, url,
__MODULE__, __MODULE__,
url, url,
[] keepalive: keepalive
) )
end 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], plt_add_apps: [:mix],
ignore_warnings: "../../.dialyzer-ignore" ignore_warnings: "../../.dialyzer-ignore"
], ],
elixir: "~> 1.6", elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock", lockfile: "../../mix.lock",
preferred_cli_env: [ preferred_cli_env: [
@ -80,7 +80,7 @@ defmodule EthereumJsonrpc.MixProject do
# Tracing # Tracing
{:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true}, {:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true},
# `:spandex` integration with Datadog # `:spandex` integration with Datadog
{:spandex_datadog, "~> 0.3.1"}, {:spandex_datadog, "~> 0.4.0"},
# Convert unix timestamps in JSONRPC to DateTimes # Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.4"}, {:timex, "~> 3.4"},
# Encode/decode function names and arguments # 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: [] types: []
} }
assert Encoder.encode_function_call({function_selector, []}) == {"get", "0x6d4ce63c"} assert Encoder.encode_function_call(function_selector, []) == "0x6d4ce63c"
end end
test "generates the correct encoding with arguments" do test "generates the correct encoding with arguments" do
@ -23,8 +23,8 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}] types: [{:uint, 256}]
} }
assert Encoder.encode_function_call({function_selector, [10]}) == assert Encoder.encode_function_call(function_selector, [10]) ==
{"get", "0x9507d39a000000000000000000000000000000000000000000000000000000000000000a"} "0x9507d39a000000000000000000000000000000000000000000000000000000000000000a"
end end
test "generates the correct encoding with addresses arguments" do test "generates the correct encoding with addresses arguments" do
@ -36,127 +36,12 @@ defmodule EthereumJSONRPC.EncoderTest do
args = ["0xdab1c67232f92b7707f49c08047b96a4db7a9fc6", "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493"] args = ["0xdab1c67232f92b7707f49c08047b96a4db7a9fc6", "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493"]
assert Encoder.encode_function_call({function_selector, args}) == assert Encoder.encode_function_call(function_selector, args) ==
{"tokens", "0x508493bc000000000000000000000000dab1c67232f92b7707f49c08047b96a4db7a9fc60000000000000000000000006937cb25eb54bc013b9c13c47ab38eb63edd1493"
"0x508493bc000000000000000000000000dab1c67232f92b7707f49c08047b96a4db7a9fc60000000000000000000000006937cb25eb54bc013b9c13c47ab38eb63edd1493"}
end end
end end
describe "get_selectors/2" do describe "decode_result/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
test "correctly decodes the blockchain result" do test "correctly decodes the blockchain result" do
result = %{ result = %{
id: "sum", id: "sum",
@ -170,7 +55,7 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}] types: [{:uint, 256}]
} }
assert Encoder.decode_result({result, selector}) == {"sum", {:ok, [42]}} assert Encoder.decode_result(result, selector) == {"sum", {:ok, [42]}}
end end
test "correctly handles the blockchain error response" do test "correctly handles the blockchain error response" do
@ -189,7 +74,7 @@ defmodule EthereumJSONRPC.EncoderTest do
types: [{:uint, 256}] 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."}} {"sum", {:error, "(-32602) Invalid params: Invalid hex: Invalid character 'x' at position 134."}}
end end
@ -199,7 +84,7 @@ defmodule EthereumJSONRPC.EncoderTest do
selector = %ABI.FunctionSelector{function: "name", types: [], returns: [:string]} 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 end
end end

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

Loading…
Cancel
Save