Merge remote-tracking branch 'origin' into vb-update-phoenix

pull/3143/head
Victor Baranov 4 years ago
commit b3e1f3902e
  1. 4
      .circleci/config.yml
  2. 10
      .dialyzer-ignore
  3. 2
      .tool-versions
  4. 63
      CHANGELOG.md
  5. 8
      apps/block_scout_web/assets/css/_helpers.scss
  6. 13
      apps/block_scout_web/assets/css/components/_card.scss
  7. 6
      apps/block_scout_web/assets/css/components/_dropdown.scss
  8. 53
      apps/block_scout_web/assets/css/components/_modal.scss
  9. 23
      apps/block_scout_web/assets/css/components/_modal_status.scss
  10. 15
      apps/block_scout_web/assets/css/components/_navbar.scss
  11. 6
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  12. 258
      apps/block_scout_web/assets/js/lib/modals.js
  13. 154
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  14. 2
      apps/block_scout_web/assets/js/lib/smart_contract/index.js
  15. 53
      apps/block_scout_web/assets/js/lib/smart_contract/read_only_functions.js
  16. 54
      apps/block_scout_web/assets/js/lib/smart_contract/write.js
  17. 1
      apps/block_scout_web/assets/js/pages/write_contract.js
  18. 2209
      apps/block_scout_web/assets/package-lock.json
  19. 6
      apps/block_scout_web/assets/package.json
  20. 1
      apps/block_scout_web/assets/webpack.config.js
  21. 4
      apps/block_scout_web/config/config.exs
  22. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  23. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  24. 39
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
  25. 45
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
  26. 39
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
  27. 30
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  28. 14
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  29. 18
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  30. 72
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  31. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  32. 90
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  33. 6
      apps/block_scout_web/lib/block_scout_web/schema/types.ex
  34. 21
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  35. 79
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  36. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  37. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/_metatags.html.eex
  38. 19
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex
  39. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/_metatags.html.eex
  40. 20
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex
  41. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/_metatags.html.eex
  42. 20
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex
  43. 31
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_status.html.eex
  44. 1
      apps/block_scout_web/lib/block_scout_web/templates/icons/_external_link.html.eex
  45. 35
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  46. 3
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  47. 37
      apps/block_scout_web/lib/block_scout_web/templates/log/_data_decoded_view.html.eex
  48. 91
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  49. 23
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex
  50. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/read_contract/index.html.eex
  51. 42
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
  52. 1
      apps/block_scout_web/lib/block_scout_web/templates/transaction/not_found.html.eex
  53. 8
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  54. 80
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  55. 17
      apps/block_scout_web/lib/block_scout_web/templates/transaction_raw_trace/index.html.eex
  56. 8
      apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
  57. 13
      apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
  58. 22
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  59. 13
      apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
  60. 13
      apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
  61. 21
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  62. 1
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  63. 22
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  64. 3
      apps/block_scout_web/lib/block_scout_web/views/log_view.ex
  65. 30
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  66. 4
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  67. 21
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  68. 8
      apps/block_scout_web/mix.exs
  69. 232
      apps/block_scout_web/priv/gettext/default.pot
  70. 232
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  71. 79
      apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
  72. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
  73. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
  74. 326
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  75. 183
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  76. 135
      apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
  77. 19
      apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs
  78. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs
  79. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs
  80. 177
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
  81. 305
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
  82. 196
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
  83. 24
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  84. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  85. 1
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  86. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  87. 16
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  88. 5
      apps/explorer/config/config.exs
  89. 25
      apps/explorer/config/dev/besu.exs
  90. 25
      apps/explorer/config/prod/besu.exs
  91. 14
      apps/explorer/config/test/besu.exs
  92. 290
      apps/explorer/lib/explorer/chain.ex
  93. 48
      apps/explorer/lib/explorer/chain/address/coin_balance.ex
  94. 33
      apps/explorer/lib/explorer/chain/log.ex
  95. 35
      apps/explorer/lib/explorer/chain/smart_contract.ex
  96. 19
      apps/explorer/lib/explorer/chain/supply/token_bridge.ex
  97. 27
      apps/explorer/lib/explorer/chain/transaction.ex
  98. 18
      apps/explorer/lib/explorer/chain_spec/genesis_data.ex
  99. 93
      apps/explorer/lib/explorer/chain_spec/geth/importer.ex
  100. 3
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  101. Some files were not shown because too many files have changed in this diff Show More

@ -233,7 +233,7 @@ jobs:
eslint: eslint:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.16.1-browsers-legacy - image: circleci/node:12.18.2-browsers-legacy
working_directory: ~/app working_directory: ~/app
@ -275,7 +275,7 @@ jobs:
jest: jest:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.16.1-browsers-legacy - image: circleci/node:12.18.2-browsers-legacy
working_directory: ~/app working_directory: ~/app

@ -1,16 +1,22 @@
: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
:0: Unknown type 'Elixir.Hash':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
lib/explorer/repo/prometheus_logger.ex:8 lib/explorer/repo/prometheus_logger.ex:8
lib/block_scout_web/views/layout_view.ex:175 lib/block_scout_web/views/layout_view.ex:175
lib/explorer/smart_contract/publisher_worker.ex:6 lib/explorer/smart_contract/publisher_worker.ex:6
lib/explorer/smart_contract/verifier.ex:84
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: 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() 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()
lib/block_scout_web/views/layout_view.ex:172: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() lib/block_scout_web/views/layout_view.ex:172: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1 lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:402
lib/block_scout_web/views/layout_view.ex:143
lib/block_scout_web/schema/types.ex:31 lib/block_scout_web/schema/types.ex:31
lib/phoenix/router.ex:324
lib/phoenix/router.ex:402
lib/block_scout_web/views/layout_view.ex:138: The call 'Elixir.Poison.Parser':'parse!'
lib/block_scout_web/views/layout_view.ex:224: The call 'Elixir.Poison.Parser':'parse!'
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22

@ -1,3 +1,3 @@
elixir 1.10.3 elixir 1.10.3
erlang 22.2 erlang 22.2
nodejs 12.14.1 nodejs 12.18.2

@ -1,10 +1,71 @@
## Current ## Current
### Features
- [#3199](https://github.com/poanetwork/blockscout/pull/3199) - Show compilation error at contract verification
- [#3193](https://github.com/poanetwork/blockscout/pull/3193) - Raw trace copy button
- [#3184](https://github.com/poanetwork/blockscout/pull/3184) - Apps navbar menu item
- [#3145](https://github.com/poanetwork/blockscout/pull/3145) - Pending txs per address API endpoint
### Fixes
- [#3209](https://github.com/poanetwork/blockscout/pull/3209) - GraphQL: fix internal server error at request of internal transactions at address
- [#3207](https://github.com/poanetwork/blockscout/pull/3207) - Fix read contract bytes array type output
- [#3203](https://github.com/poanetwork/blockscout/pull/3203) - Improve "get mined blocks" query performance
- [#3202](https://github.com/poanetwork/blockscout/pull/3202) - Fix contracts verification with experimental features enabled
- [#3201](https://github.com/poanetwork/blockscout/pull/3201) - Connect to Metamask button
- [#3192](https://github.com/poanetwork/blockscout/pull/3192) - Dropdown menu doesn't open at "not found" page
- [#3190](https://github.com/poanetwork/blockscout/pull/3190) - Contract log/method decoded view improvements: eliminate horizontal scroll, remove excess borders, whitespaces
- [#3185](https://github.com/poanetwork/blockscout/pull/3185) - Transaction page: decoding logs from nested contracts calls
- [#3182](https://github.com/poanetwork/blockscout/pull/3182) - Besu: support revertReason key in eth_getTransactionReceipt endpoint
- [#3178](https://github.com/poanetwork/blockscout/pull/3178) - Fix permanent fetching tokens... when read/write proxy tab is active
- [#3178](https://github.com/poanetwork/blockscout/pull/3178) - Fix unavailable navbar menu when read/write proxy tab is active
### Chore
- [#3143](https://github.com/poanetwork/blockscout/pull/3143) - Update Phoenix 1.4 -> 1.5
- [#3210](https://github.com/poanetwork/blockscout/pull/3210) - Update Phoenix up to 1.4.17
- [#3206](https://github.com/poanetwork/blockscout/pull/3206) - Update Elixir version: 1.10.2 -> 1.10.3
- [#3204](https://github.com/poanetwork/blockscout/pull/3204) - GraphQL Absinthe related packages update up to stable versions
- [#3180](https://github.com/poanetwork/blockscout/pull/3180) - Return correct status in verify API endpoint if contract verified
- [#3180](https://github.com/poanetwork/blockscout/pull/3180) - Remove Kovan from the list of default chains
## 3.3.0-beta
### Features
- [#3174](https://github.com/poanetwork/blockscout/pull/3174) - EIP-1967 support: transparent proxy pattern
- [#3173](https://github.com/poanetwork/blockscout/pull/3173) - Display implementation address at read/write proxy tabs
- [#3171](https://github.com/poanetwork/blockscout/pull/3171) - Import accounts/contracts/balances from Geth genesis.json
- [#3161](https://github.com/poanetwork/blockscout/pull/3161) - Write proxy contracts feature
- [#3160](https://github.com/poanetwork/blockscout/pull/3160) - Write contracts feature
- [#3157](https://github.com/poanetwork/blockscout/pull/3157) - Read methods of implementation on proxy contract
### Fixes
- [#3168](https://github.com/poanetwork/blockscout/pull/3168) - Eliminate internal server error at /accounts page with token-bridge type of supply and inexistent bridge contracts
- [#3169](https://github.com/poanetwork/blockscout/pull/3169) - Fix for verification of contracts defined in genesis block
### Chore
## 3.2.0-beta
### Features
- [#3154](https://github.com/poanetwork/blockscout/pull/3154) - Support of Hyperledger Besu client
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: logs decoding using implementation ABI
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: methods decoding using implementation ABI
- [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint.
### Fixes
### Chore
- [#3152](https://github.com/poanetwork/blockscout/pull/3152) - Fix contract compilation tests for old versions of compiler
## 3.1.3-beta
### Features ### Features
- [#3125](https://github.com/poanetwork/blockscout/pull/3125) - Availability to configure a number of days to consider at coin balance history chart via environment variable - [#3125](https://github.com/poanetwork/blockscout/pull/3125) - Availability to configure a number of days to consider at coin balance history chart via environment variable
### Fixes ### Fixes
- [#3143](https://github.com/poanetwork/blockscout/pull/3143) - Update Phoenix 1.4 -> 1.5, Elixir 1.10.2 -> 1.10.3 - [#3146](https://github.com/poanetwork/blockscout/pull/3146) - Fix coin balance history page: order of items, fix if no balance changes
- [#3142](https://github.com/poanetwork/blockscout/pull/3142) - Speed-up last coin balance timestamp query (coin balance history page performance improvement) - [#3142](https://github.com/poanetwork/blockscout/pull/3142) - Speed-up last coin balance timestamp query (coin balance history page performance improvement)
- [#3140](https://github.com/poanetwork/blockscout/pull/3140) - Fix performance of the balance changing history list loading - [#3140](https://github.com/poanetwork/blockscout/pull/3140) - Fix performance of the balance changing history list loading
- [#3133](https://github.com/poanetwork/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests - [#3133](https://github.com/poanetwork/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests

@ -29,3 +29,11 @@
height: 0; height: 0;
visibility: hidden; visibility: hidden;
} }
.hidden {
display: none!important;
}
.word-break-all {
word-break: break-all;
}

@ -257,3 +257,16 @@ $card-tab-icon-color-active: #fff !default;
margin-left: 15px!important; margin-left: 15px!important;
} }
} }
.implementation-container {
margin-top: 10px;
}
.implementation-title {
float: left;
margin-right: 5px;
}
.implementation-value {
line-height: 30px;
}

@ -42,6 +42,12 @@ $dropdown-menu-item-hover-background: rgba($secondary, 0.1) !default;
&:focus { &:focus {
background-color: $dropdown-menu-item-hover-background; background-color: $dropdown-menu-item-hover-background;
color: $dropdown-menu-item-hover-color; color: $dropdown-menu-item-hover-color;
.external-link-icon {
path {
fill: $header-icon-color-hover;
}
}
} }
} }

@ -11,6 +11,11 @@
padding: #{$modal-vertical-padding} #{$modal-horizontal-padding}; padding: #{$modal-vertical-padding} #{$modal-horizontal-padding};
} }
.modal-header-group {
display: inline-flex;
margin: 0 auto;
}
.close.close-modal{ .close.close-modal{
left: auto; left: auto;
opacity: 1; opacity: 1;
@ -18,6 +23,10 @@
right: -35px; right: -35px;
top: -35px; top: -35px;
outline: none !important; outline: none !important;
path {
fill: #F6F7F9;
}
} }
.close { .close {
@ -35,6 +44,10 @@
text-align: left; text-align: left;
white-space: normal; white-space: normal;
word-break: break-word; word-break: break-word;
.centered {
margin: 0 auto;
}
} }
.modal-content { .modal-content {
@ -55,3 +68,43 @@
} }
} }
} }
.modal-fullwidth-xs {
@include media-breakpoint-down(xs) {
padding-right: 0 !important;
.modal-dialog {
max-width: initial;
min-width: initial;
margin: 0.5rem 0;
}
.modal-content {
border-radius: 0;
> div {
border-radius: 0;
}
}
.close.close-modal {
right: 10px;
top: 5px;
path {
fill: #a3a9b5;
}
}
.modal-bottom-disclaimer {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
}
@media (min-width: 576px) {
.modal-dialog {
max-width: 530px !important;
}
}

@ -15,8 +15,8 @@ $modal-status-graph-question: #329ae9 !default;
.modal-status-graph { .modal-status-graph {
align-items: center; align-items: center;
border-top-left-radius: $modal-border-radius; border-top-left-radius: 8px;
border-top-right-radius: $modal-border-radius; border-top-right-radius: 8px;
display: flex; display: flex;
height: 135px; height: 135px;
justify-content: center; justify-content: center;
@ -66,6 +66,18 @@ $modal-status-graph-question: #329ae9 !default;
line-height: 1.5; line-height: 1.5;
margin: 0 0 25px; margin: 0 0 25px;
text-align: center; text-align: center;
&.m-b-0 {
margin-bottom: 0;
}
.link-helptip {
border-bottom-width: 1px;
border-bottom-style: dotted;
color: inherit;
cursor: help;
text-decoration: none;
}
} }
.modal-status-button-wrapper { .modal-status-button-wrapper {
@ -76,6 +88,13 @@ $modal-status-graph-question: #329ae9 !default;
.btn-line { .btn-line {
flex-grow: 1; flex-grow: 1;
margin-right: 20px; margin-right: 20px;
border-color: $primary;
color: $primary;
&:hover {
background-color: $primary;
color: $additional-font;
}
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;

@ -260,3 +260,18 @@ $navbar-logo-width: auto !default;
transition: none !important; transition: none !important;
} }
} }
.external-link-icon {
float: right;
margin-top: -1px;
& {
&.active,
&:hover,
&:focus {
path {
fill: $header-icon-color-hover;
}
}
}
}

@ -215,6 +215,12 @@ $container-max-widths: (
@include _assert-ascending($container-max-widths, "$container-max-widths"); @include _assert-ascending($container-max-widths, "$container-max-widths");
@media (min-width: 1200px) {
.container {
max-width: 1260px !important;
}
}
// Grid columns // Grid columns
// //
// Set the number of columns and specify the width of the gutters. // Set the number of columns and specify the width of the gutters.

@ -1,75 +1,191 @@
import $ from 'jquery' import $ from 'jquery'
$(function () { let $currentModal = null
$('.js-become-candidate').on('click', function () { let modalLocked = false
$('#becomeCandidateModal').modal()
}) const spinner =
`
$('.js-validator-info-modal').on('click', function () { <span class="loading-spinner-small mr-2">
$('#validatorInfoModal').modal() <span class="loading-spinner-block-1"></span>
}) <span class="loading-spinner-block-2"></span>
</span>
$('.js-move-stake').on('click', function () { `
$('#errorStatusModal').modal()
}) $(document.body).on('hide.bs.modal', e => {
if (modalLocked) {
$('.js-remove-pool').on('click', function () { e.preventDefault()
$('#warningStatusModal').modal() e.stopPropagation()
}) return false
$('.js-copy-address').on('click', function () {
$('#successStatusModal').modal()
})
$('.js-stake-stake').on('click', function () {
const modal = '#stakeModal'
const progress = parseInt($(`${modal} .js-stakes-progress-data-progress`).text())
const total = parseInt($(`${modal} .js-stakes-progress-data-total`).text())
$(modal).modal()
setupStakesProgress(progress, total, modal)
})
$('.js-withdraw-stake').on('click', function () {
const modal = '#withdrawModal'
const progress = parseInt($(`${modal} .js-stakes-progress-data-progress`).text())
const total = parseInt($(`${modal} .js-stakes-progress-data-total`).text())
$(modal).modal()
setupStakesProgress(progress, total, modal)
})
function setupStakesProgress (progress, total, modal) {
// const stakeProgress = $(`${modal} .js-stakes-progress`)
// const primaryColor = $('.btn-full-primary').css('background-color')
// const backgroundColors = [
// primaryColor,
// 'rgba(202, 199, 226, 0.5)'
// ]
// const progressBackground = total - progress
// // eslint-disable-next-line no-unused-vars
// const myChart = new window.Chart(stakeProgress, {
// type: 'doughnut',
// data: {
// datasets: [{
// data: [progress, progressBackground],
// backgroundColor: backgroundColors,
// hoverBackgroundColor: backgroundColors,
// borderWidth: 0
// }]
// },
// options: {
// cutoutPercentage: 80,
// legend: {
// display: false
// },
// tooltips: {
// enabled: false
// }
// }
// })
} }
$currentModal = null
}) })
export function currentModal () {
return $currentModal
}
export function openModal ($modal, unclosable) {
// Hide all tooltips before showing a modal,
// since they are sticking on top of modal
$('.tooltip').tooltip('hide')
if (unclosable) {
$('.close-modal, .modal-status-button-wrapper', $modal).addClass('hidden')
$('.modal-status-text', $modal).addClass('m-b-0')
}
if ($currentModal) {
modalLocked = false
$currentModal
.one('hidden.bs.modal', () => {
$modal.modal('show')
$currentModal = $modal
if (unclosable) {
modalLocked = true
}
})
.modal('hide')
} else {
$modal.modal('show')
$currentModal = $modal
if (unclosable) {
modalLocked = true
}
}
}
export function openModalWithMessage ($modal, unclosable, message) {
$modal.find('.modal-message').text(message)
openModal($modal, unclosable)
}
export function lockModal ($modal, $submitButton = null, spinnerText = '') {
$modal.find('.close-modal').attr('disabled', true)
const $button = $submitButton || $modal.find('.btn-add-full')
$button
.attr('data-text', $button.text())
.attr('disabled', true)
const $span = $('span', $button)
const waitHtml = spinner + (spinnerText ? ` ${spinnerText}` : '')
if ($span.length) {
$('svg', $button).hide()
$span.html(waitHtml)
} else {
$button.html(waitHtml)
}
modalLocked = true
}
export function unlockModal ($modal, $submitButton = null) {
$modal.find('.close-modal').attr('disabled', false)
const $button = $submitButton || $modal.find('.btn-add-full')
const buttonText = $button.attr('data-text')
$button.attr('disabled', false)
const $span = $('span', $button)
if ($span.length) {
$('svg', $button).show()
$span.text(buttonText)
} else {
$button.text(buttonText)
}
modalLocked = false
}
export function openErrorModal (title, text, unclosable) {
const $modal = $('#errorStatusModal')
$modal.find('.modal-status-title').text(title)
$modal.find('.modal-status-text').html(text)
openModal($modal, unclosable)
}
export function openWarningModal (title, text) {
const $modal = $('#warningStatusModal')
$modal.find('.modal-status-title').text(title)
$modal.find('.modal-status-text').html(text)
openModal($modal)
}
export function openSuccessModal (title, text) {
const $modal = $('#successStatusModal')
$modal.find('.modal-status-title').text(title)
$modal.find('.modal-status-text').html(text)
openModal($modal)
}
export function openQuestionModal (title, text, acceptCallback = null, exceptCallback = null, acceptText = 'Yes', exceptText = 'No') {
const $modal = $('#questionStatusModal')
const $closeButton = $modal.find('.close-modal')
$closeButton.attr('disabled', false)
$modal.find('.modal-status-title').text(title)
$modal.find('.modal-status-text').text(text)
const $accept = $modal.find('.btn-line.accept')
const $except = $modal.find('.btn-line.except')
$accept
.removeAttr('data-dismiss')
.removeAttr('disabled')
.unbind('click')
.find('.btn-line-text').text(acceptText)
$except
.removeAttr('data-dismiss')
.removeAttr('disabled')
.unbind('click')
.find('.btn-line-text').text(exceptText)
if (acceptCallback) {
$accept.on('click', event => {
$closeButton.attr('disabled', true)
$accept
.unbind('click')
.attr('disabled', true)
.find('.btn-line-text').html(spinner)
$except
.unbind('click')
.removeAttr('data-dismiss')
.attr('disabled', true)
modalLocked = true
acceptCallback($modal, event)
})
} else {
$accept.attr('data-dismiss', 'modal')
}
if (exceptCallback) {
$except.on('click', event => {
$closeButton.attr('disabled', true)
$except
.unbind('click')
.attr('disabled', true)
.find('.btn-line-text').html(spinner)
$accept
.unbind('click')
.attr('disabled', true)
.removeAttr('data-dismiss')
modalLocked = true
exceptCallback($modal, event)
})
} else {
$except.attr('data-dismiss', 'modal')
}
openModal($modal)
}

@ -0,0 +1,154 @@
import $ from 'jquery'
import ethNetProps from 'eth-net-props'
import { walletEnabled, connectToWallet, getCurrentAccount, hideConnectButton } from './write.js'
import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals.js'
import '../../pages/address'
const WEI_MULTIPLIER = 10 ** 18
const loadFunctions = (element) => {
const $element = $(element)
const url = $element.data('url')
const hash = $element.data('hash')
const type = $element.data('type')
const action = $element.data('action')
$.get(
url,
{ hash: hash, type: type, action: action },
response => $element.html(response)
)
.done(function () {
const $connect = $('[connect-metamask]')
if (hideConnectButton()) {
$connect.addClass('hidden')
} else {
$connect.removeClass('hidden')
}
$connect.on('click', () => {
connectToWallet()
})
$('[data-function]').each((_, element) => {
readWriteFunction(element)
})
})
.fail(function (response) {
$element.html(response.statusText)
})
}
const readWriteFunction = (element) => {
const $element = $(element)
const $form = $element.find('[data-function-form]')
const $responseContainer = $element.find('[data-function-response]')
$form.on('submit', (event) => {
const action = $form.data('action')
event.preventDefault()
if (action === 'read') {
const url = $form.data('url')
const $functionName = $form.find('input[name=function_name]')
const $functionInputs = $form.find('input[name=function_input]')
const args = $.map($functionInputs, element => {
return $(element).val()
})
const data = {
function_name: $functionName.val(),
args
}
$.get(url, data, response => $responseContainer.html(response))
} else if (action === 'write') {
const chainId = $form.data('chainId')
walletEnabled()
.then((isWalletEnabled) => {
if (isWalletEnabled) {
const functionName = $form.find('input[name=function_name]').val()
const $functionInputs = $form.find('input[name=function_input]')
const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])')
const args = $.map($functionInputsExceptTxValue, element => $(element).val())
const $txValue = $functionInputs.filter('[tx-value]:first')
const txValue = $txValue && $txValue.val() && parseFloat($txValue.val()) * WEI_MULTIPLIER
const contractAddress = $form.data('contract-address')
const implementationAbi = $form.data('implementation-abi')
const parentAbi = $form.data('contract-abi')
const $parent = $('[data-smart-contract-functions]')
const contractType = $parent.data('type')
const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi
window.web3.eth.getChainId()
.then(chainIdFromWallet => {
if (chainId !== chainIdFromWallet) {
const networkDisplayNameFromWallet = ethNetProps.props.getNetworkDisplayName(chainIdFromWallet)
const networkDisplayName = ethNetProps.props.getNetworkDisplayName(chainId)
return Promise.reject(new Error(`You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain`))
} else {
return getCurrentAccount()
}
})
.then(currentAccount => {
let methodToCall
if (functionName) {
const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress)
methodToCall = TargetContract.methods[functionName](...args).send({ from: currentAccount, value: txValue || 0 })
} else {
const txParams = {
from: currentAccount,
to: contractAddress,
value: txValue || 0
}
methodToCall = window.web3.eth.sendTransaction(txParams)
}
methodToCall
.on('error', function (error) {
openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false)
})
.on('transactionHash', function (txHash) {
openModalWithMessage($element.find('#pending-contract-write'), true, txHash)
const getTxReceipt = (txHash) => {
window.web3.eth.getTransactionReceipt(txHash)
.then(txReceipt => {
if (txReceipt) {
openSuccessModal('Success', `Successfully sent <a href="/tx/${txHash}">transaction</a> for method "${functionName}"`)
clearInterval(txReceiptPollingIntervalId)
}
})
}
const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000)
})
})
.catch(error => {
openWarningModal('Unauthorized', formatError(error))
})
} else {
openWarningModal('Unauthorized', 'You haven\'t approved the reading of account list from your MetaMask or MetaMask/Nifty wallet is locked or is not installed.')
}
})
}
})
}
const formatError = (error) => {
let { message } = error
message = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message
return message
}
const container = $('[data-smart-contract-functions]')
if (container.length) {
loadFunctions(container)
}

@ -1,3 +1,3 @@
import './read_only_functions' import './functions'
import './wei_ether_converter' import './wei_ether_converter'
import '../../app' import '../../app'

@ -1,53 +0,0 @@
import $ from 'jquery'
const loadFunctions = (element) => {
const $element = $(element)
const url = $element.data('url')
const hash = $element.data('hash')
$.get(
url,
{ hash: hash },
response => $element.html(response)
)
.done(function () {
$('[data-function]').each((_, element) => {
readFunction(element)
})
})
.fail(function (response) {
$element.html(response.statusText)
})
}
const readFunction = (element) => {
const $element = $(element)
const $form = $element.find('[data-function-form]')
const $responseContainer = $element.find('[data-function-response]')
$form.on('submit', (event) => {
event.preventDefault()
const url = $form.data('url')
const $functionName = $form.find('input[name=function_name]')
const $functionInputs = $form.find('input[name=function_input]')
const args = $.map($functionInputs, element => {
return $(element).val()
})
const data = {
function_name: $functionName.val(),
args
}
$.get(url, data, response => $responseContainer.html(response))
})
}
const container = $('[data-smart-contract-functions]')
if (container.length) {
loadFunctions(container)
}

@ -0,0 +1,54 @@
import Web3 from 'web3'
export const walletEnabled = () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
if (window.ethereum.isUnlocked && window.ethereum.isNiftyWallet) { // Nifty Wallet
window.web3 = new Web3(window.web3.currentProvider)
return Promise.resolve(true)
} else if (window.ethereum.isUnlocked === false && window.ethereum.isNiftyWallet) { // Nifty Wallet
return Promise.resolve(false)
} else {
window.ethereum.enable()
window.web3 = new Web3(window.web3.currentProvider)
return Promise.resolve(true)
}
} else if (window.web3) {
window.web3 = new Web3(window.web3.currentProvider)
return Promise.resolve(true)
} else {
return Promise.resolve(false)
}
}
export const connectToWallet = () => {
if (window.ethereum) {
window.ethereum.enable()
}
}
export const getCurrentAccount = async () => {
const accounts = await window.web3.eth.getAccounts()
const account = accounts[0] ? accounts[0].toLowerCase() : null
return account
}
export const hideConnectButton = () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
if (window.ethereum.isNiftyWallet) {
return true
} else if (window.ethereum.isMetaMask) {
if (window.ethereum.selectedAddress) {
return true
} else {
return false
}
} else {
return true
}
} else {
return false
}
}

@ -0,0 +1 @@
import '../lib/smart_contract/write'

File diff suppressed because it is too large Load Diff

@ -25,11 +25,12 @@
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"eth-net-props": "^1.0.33",
"highlight.js": "^9.16.2", "highlight.js": "^9.16.2",
"highlightjs-solidity": "^1.0.8", "highlightjs-solidity": "^1.0.8",
"humps": "^2.0.1", "humps": "^2.0.1",
"jquery": "^3.4.0", "jquery": "^3.4.0",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"moment": "^2.24.0", "moment": "^2.24.0",
"nanomorph": "^5.4.0", "nanomorph": "^5.4.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
@ -39,7 +40,8 @@
"popper.js": "^1.14.7", "popper.js": "^1.14.7",
"reduce-reducers": "^0.4.3", "reduce-reducers": "^0.4.3",
"redux": "^4.0.5", "redux": "^4.0.5",
"urijs": "^1.19.2" "urijs": "^1.19.2",
"web3": "^1.2.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.2", "@babel/core": "^7.7.2",

@ -90,6 +90,7 @@ const appJs =
'admin-tasks': './js/pages/admin/tasks.js', 'admin-tasks': './js/pages/admin/tasks.js',
'read-token-contract': './js/pages/read_token_contract.js', 'read-token-contract': './js/pages/read_token_contract.js',
'smart-contract-helpers': './js/lib/smart_contract/index.js', 'smart-contract-helpers': './js/lib/smart_contract/index.js',
'write_contract': './js/pages/write_contract.js',
'token-transfers-toggle': './js/lib/token_transfers_toggle.js', 'token-transfers-toggle': './js/lib/token_transfers_toggle.js',
'try-api': './js/lib/try_api.js', 'try-api': './js/lib/try_api.js',
'try-eth-api': './js/lib/try_eth_api.js', 'try-eth-api': './js/lib/try_eth_api.js',

@ -32,7 +32,9 @@ config :block_scout_web,
}, },
other_networks: System.get_env("SUPPORTED_CHAINS"), other_networks: System.get_env("SUPPORTED_CHAINS"),
webapp_url: System.get_env("WEBAPP_URL"), webapp_url: System.get_env("WEBAPP_URL"),
api_url: System.get_env("API_URL") api_url: System.get_env("API_URL"),
apps_menu: if(System.get_env("APPS_MENU", "false") == "true", do: true, else: false),
external_apps: System.get_env("EXTERNAL_APPS")
config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true

@ -23,16 +23,6 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
{coin_balances, next_page} = split_list_by_page(coin_balances_plus_one) {coin_balances, next_page} = split_list_by_page(coin_balances_plus_one)
deduplicated_coin_balances =
coin_balances
|> Enum.dedup_by(fn record ->
if record.delta == Decimal.new(0) do
:dup
else
System.unique_integer()
end
end)
next_page_url = next_page_url =
case next_page_params(next_page, coin_balances, params) do case next_page_params(next_page, coin_balances, params) do
nil -> nil ->
@ -48,7 +38,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
end end
coin_balances_json = coin_balances_json =
Enum.map(deduplicated_coin_balances, fn coin_balance -> Enum.map(coin_balances, fn coin_balance ->
View.render_to_string( View.render_to_string(
AddressCoinBalanceView, AddressCoinBalanceView,
"_coin_balances.html", "_coin_balances.html",

@ -31,6 +31,8 @@ defmodule BlockScoutWeb.AddressReadContractController do
conn, conn,
"index.html", "index.html",
address: address, address: address,
type: :regular,
action: :read,
coin_balance_status: CoinBalanceOnDemand.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(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}) counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})

@ -0,0 +1,39 @@
# credo:disable-for-this-file
defmodule BlockScoutWeb.AddressReadProxyController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract) do
render(
conn,
"index.html",
address: address,
type: :proxy,
action: :read,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
)
else
_ ->
not_found(conn)
end
end
end

@ -0,0 +1,45 @@
# credo:disable-for-this-file
#
# When moving the calls to ajax, this controller became very similar to the
# `address_contract_controller`, but both are necessary until we are able to
# address a better way to organize the controllers.
#
# So, for now, I'm adding this comment to disable the credo check for this file.
defmodule BlockScoutWeb.AddressWriteContractController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract) do
render(
conn,
"index.html",
address: address,
type: :regular,
action: :write,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
)
else
_ ->
not_found(conn)
end
end
end

@ -0,0 +1,39 @@
# credo:disable-for-this-file
defmodule BlockScoutWeb.AddressWriteProxyController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract) do
render(
conn,
"index.html",
address: address,
type: :proxy,
action: :write,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
)
else
_ ->
not_found(conn)
end
end
end

@ -71,6 +71,29 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
balance(conn, params, :balancemulti) balance(conn, params, :balancemulti)
end end
def pendingtxlist(conn, params) do
options = optional_params(params)
with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hash}} <- to_address_hash(address_param),
{:ok, transactions} <- list_pending_transactions(address_hash, options) do
render(conn, :pendingtxlist, %{transactions: transactions})
else
{:address_param, :error} ->
conn
|> put_status(200)
|> render(:error, error: "Query parameter 'address' is required")
{:format, :error} ->
conn
|> put_status(200)
|> render(:error, error: "Invalid address format")
{:error, :not_found} ->
render(conn, :error, error: "No transactions found", data: [])
end
end
def txlist(conn, params) do def txlist(conn, params) do
options = optional_params(params) options = optional_params(params)
@ -457,6 +480,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end end
end end
defp list_pending_transactions(address_hash, options) do
case Etherscan.list_pending_transactions(address_hash, options) do
[] -> {:error, :not_found}
pending_transactions -> {:ok, pending_transactions}
end
end
defp list_internal_transactions(transaction_hash) do defp list_internal_transactions(transaction_hash) do
case Etherscan.list_internal_transactions(transaction_hash) do case Etherscan.list_internal_transactions(transaction_hash) do
[] -> {:error, :not_found} [] -> {:error, :not_found}

@ -17,6 +17,20 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
render(conn, :verify, %{contract: address}) render(conn, :verify, %{contract: address})
else else
{:publish,
{:error,
%Ecto.Changeset{
errors: [
address_hash:
{"has already been taken",
[
constraint: :unique,
constraint_name: "smart_contracts_address_hash_index"
]}
]
}}} ->
render(conn, :error, error: "Smart-contract already verified.")
{:publish, _} -> {:publish, _} ->
render(conn, :error, error: "Something went wrong while publishing the contract.") render(conn, :error, error: "Something went wrong while publishing the contract.")

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

@ -2,21 +2,64 @@ defmodule BlockScoutWeb.SmartContractController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.Chain alias Explorer.Chain
alias Explorer.SmartContract.Reader alias Explorer.SmartContract.{Reader, Writer}
def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do
address_options = [
necessity_by_association: %{
:smart_contract => :optional
}
]
def index(conn, %{"hash" => address_hash_string}) do
with true <- ajax?(conn), with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
read_only_functions = Reader.read_only_functions(address_hash) implementation_address_hash_string =
if contract_type == "proxy" do
Chain.get_implementation_address_hash(address.hash, address.smart_contract.abi) ||
"0x0000000000000000000000000000000000000000"
else
"0x0000000000000000000000000000000000000000"
end
functions =
if action == "write" do
if contract_type == "proxy" do
Writer.write_functions_proxy(implementation_address_hash_string)
else
Writer.write_functions(address_hash)
end
else
if contract_type == "proxy" do
Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string)
else
Reader.read_only_functions(address_hash)
end
end
contract_abi = Poison.encode!(address.smart_contract.abi)
implementation_abi =
if contract_type == "proxy" do
implementation_address_hash_string
|> Chain.get_implementation_abi()
|> Poison.encode!()
else
[]
end
conn conn
|> put_status(200) |> put_status(200)
|> put_layout(false) |> put_layout(false)
|> render( |> render(
"_functions.html", "_functions.html",
read_only_functions: read_only_functions, read_only_functions: functions,
address: address address: address,
contract_abi: contract_abi,
implementation_address: implementation_address_hash_string,
implementation_abi: implementation_abi,
contract_type: contract_type,
action: action
) )
else else
:error -> :error ->
@ -33,13 +76,26 @@ defmodule BlockScoutWeb.SmartContractController do
def index(conn, _), do: not_found(conn) def index(conn, _), do: not_found(conn)
def show(conn, params) do def show(conn, params) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with true <- ajax?(conn), with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
:ok <- Chain.check_contract_address_exists(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
contract_type = if Chain.is_proxy_contract?(address.smart_contract.abi), do: :proxy, else: :regular
outputs = outputs =
Reader.query_function( Reader.query_function(
address_hash, address_hash,
%{name: params["function_name"], args: params["args"]} %{name: params["function_name"], args: params["args"]},
contract_type
) )
conn conn

@ -13,6 +13,8 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
render( render(
conn, conn,
"index.html", "index.html",
type: :regular,
action: :read,
token: Market.add_price(token), token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}) counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
) )

@ -37,6 +37,27 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_pendingtxlist_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"hash" => "0x98beb27135aa0a25650557005ad962919d6a278c4b3dde7f4f6a3a1e65aa746c",
"nonce" => "0",
"from" => "0x3fb1cd2cd96c6d5c0b5eb3322d807b34482481d4",
"to" => "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
"value" => "0",
"gas" => "122261",
"gasPrice" => "50000000000",
"input" =>
"0xf00d4b5d000000000000000000000000036c8cecce8d8bbf0831d840d7f29c9e3ddefa63000000000000000000000000c5a96db085dda36ffbe390f455315d30d6d3dc52",
"contractAddress" => "",
"cumulativeGasUsed" => "122207",
"gasUsed" => "122207"
}
]
}
@account_txlist_example_value %{ @account_txlist_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -470,7 +491,8 @@ defmodule BlockScoutWeb.Etherscan do
"success" => true, "success" => true,
"timeStamp" => "1541018182", "timeStamp" => "1541018182",
"to" => "0x000000000000000000000000000000000000000d", "to" => "0x000000000000000000000000000000000000000d",
"value" => "67612" "value" => "67612",
revertReason: "No credit of that type"
} }
} }
@ -609,6 +631,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("18") example: ~s("18")
} }
@revert_reason_type %{
type: "revert_reason",
definition: "Revert reason of transaction.",
example: ~s("No credit of that type")
}
@logs_details %{ @logs_details %{
name: "Log Detail", name: "Log Detail",
fields: %{ fields: %{
@ -1010,7 +1038,8 @@ defmodule BlockScoutWeb.Etherscan do
logs: %{ logs: %{
type: "array", type: "array",
array_type: @logs_details array_type: @logs_details
} },
revertReason: @revert_reason_type
} }
} }
@ -1187,6 +1216,56 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_pendingtxlist_action %{
name: "pendingtxlist",
description: "Get pending transactions by address.",
required_params: [
%{
key: "address",
placeholder: "addressHash",
type: "string",
description: "A 160-bit code used for identifying Accounts."
}
],
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_pendingtxlist_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @transaction
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@account_txlist_example_value_error)
}
]
}
@account_txlist_action %{ @account_txlist_action %{
name: "txlist", name: "txlist",
description: description:
@ -2021,9 +2100,9 @@ defmodule BlockScoutWeb.Etherscan do
<div class='tab-pane fade show active'> <div class='tab-pane fade show active'>
<div class="tile tile-muted p-1"> <div class="tile tile-muted p-1">
<div class="m-2"> <div class="m-2">
curl -d '{"addressHash":"0xd6984e092b51337032cf0300c7291e4839be37e1","compilerVersion":"v0.5.4+commit.9549d8ff", curl -d '{"addressHash":"0xc63BB6555C90846afACaC08A0F0Aa5caFCB382a1","compilerVersion":"v0.5.4+commit.9549d8ff",
"contractSourceCode":"pragma solidity ^0.5.4;\n","name":"Test","optimization":false}' "contractSourceCode":"pragma solidity ^0.5.4; \ncontract Test {\n}","name":"Test","optimization":false}'
-H "Content-Type: application/json" -X POST "https://blockscout.com/eth/kovan/api?module=contract&action=verify" -H "Content-Type: application/json" -X POST "https://blockscout.com/poa/sokol/api?module=contract&action=verify"
</pre> </pre>
</div> </div>
</div> </div>
@ -2343,6 +2422,7 @@ defmodule BlockScoutWeb.Etherscan do
@account_eth_get_balance_action, @account_eth_get_balance_action,
@account_balance_action, @account_balance_action,
@account_balancemulti_action, @account_balancemulti_action,
@account_pendingtxlist_action,
@account_txlist_action, @account_txlist_action,
@account_txlistinternal_action, @account_txlistinternal_action,
@account_tokentx_action, @account_tokentx_action,

@ -41,6 +41,9 @@ defmodule BlockScoutWeb.Schema.Types do
%{last: last}, child_complexity -> %{last: last}, child_complexity ->
last * child_complexity last * child_complexity
%{}, _child_complexity ->
0
end) end)
end end
end end
@ -153,6 +156,9 @@ defmodule BlockScoutWeb.Schema.Types do
%{last: last}, child_complexity -> %{last: last}, child_complexity ->
last * child_complexity last * child_complexity
%{}, _child_complexity ->
0
end) end)
end end
end end

@ -64,4 +64,25 @@
class: "card-tab #{tab_status("read_contract", @conn.request_path)}") class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%> %>
<% end %> <% end %>
<%= if smart_contract_is_proxy?(@address) do %>
<%= link(
gettext("Read Proxy"),
to: address_read_proxy_path(@conn, :index, @address.hash),
class: "card-tab #{tab_status("read_proxy", @conn.request_path)}")
%>
<% end %>
<%= if smart_contract_with_write_functions?(@address) do %>
<%= link(
gettext("Write Contract"),
to: address_write_contract_path(@conn, :index, @address.hash),
class: "card-tab #{tab_status("write_contract", @conn.request_path)}")
%>
<% end %>
<%= if smart_contract_with_write_functions?(@address) && smart_contract_is_proxy?(@address) do %>
<%= link(
gettext("Write Proxy"),
to: address_write_proxy_path(@conn, :index, @address.hash),
class: "card-tab #{tab_status("write_proxy", @conn.request_path)}")
%>
<% end %>
</div> </div>

@ -46,45 +46,7 @@
<td colspan="3"><code><%= text %></code></td> <td colspan="3"><code><%= text %></code></td>
</tr> </tr>
</table> </table>
<div class="table-responsive text-center"> <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<table style="color: black;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td>
<pre class="transaction-input-text tile"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div>
<% {:error, :contract_not_verified, results} -> %> <% {:error, :contract_not_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %> <%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt> <dt class="col-md-2"><%= gettext "Decoded" %></dt>
@ -99,44 +61,7 @@
<td colspan="3"><code><%= text %></code></td> <td colspan="3"><code><%= text %></code></td>
</tr> </tr>
</table> </table>
<div class="table-responsive text-center"> <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<table style="color: black;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td>
<pre class="transaction-input-text tile"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div> </div>
<% end %> <% end %>
<% _ -> %> <% _ -> %>

@ -5,7 +5,7 @@
<div class="card"> <div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX --> <!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-url="<%= smart_contract_path(@conn, :index) %>"> <div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div> <div>
<span class="loading-spinner-small mr-2"> <span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>

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

@ -0,0 +1,19 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading...") %>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
</section>

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

@ -0,0 +1,20 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading...") %>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/write_contract.js") %>"></script>
</section>

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

@ -0,0 +1,20 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading...") %>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/write_contract.js") %>"></script>
</section>

@ -3,44 +3,25 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-status-graph modal-status-graph-<%= if assigns[:status] do @status end %>"> <div class="modal-status-graph modal-status-graph-<%= if assigns[:status] do @status end %>">
<%= <%=
if @status == "error" do if @status in ["error", "success", "warning", "question"] do
render BlockScoutWeb.CommonComponentsView, "_icon_error_modal.html" render BlockScoutWeb.CommonComponentsView, "_icon_#{@status}_modal.html"
end
%>
<%=
if @status == "success" do
render BlockScoutWeb.CommonComponentsView, "_icon_success_modal.html"
end
%>
<%=
if @status == "warning" do
render BlockScoutWeb.CommonComponentsView, "_icon_warning_modal.html"
end
%>
<%=
if @status == "question" do
render BlockScoutWeb.CommonComponentsView, "_icon_question_modal.html"
end end
%> %>
</div> </div>
<%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %> <%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %>
<div class="modal-body modal-status-body"> <div class="modal-body modal-status-body">
<%= if assigns[:title] do %> <h2 class="modal-status-title"><%= if assigns[:title] do @title end %></h2>
<h2 class="modal-status-title"><%= @title %></h2> <p class="modal-status-text"><%= if assigns[:text] do @text end %></p>
<% end %>
<%= if assigns[:text] do %>
<p class="modal-status-text"><%= @text %></p>
<% end %>
<div class="modal-status-button-wrapper"> <div class="modal-status-button-wrapper">
<%= if @status !== "question" do %> <%= if @status !== "question" do %>
<button class="btn-line" type="button" data-dismiss="modal"> <button class="btn-line" type="button" data-dismiss="modal">
<span class="btn-line-text">Ok</span> <span class="btn-line-text">Ok</span>
</button> </button>
<% else %> <% else %>
<button class="btn-line except" type="button" data-dismiss="modal"> <button class="btn-line except" type="button">
<span class="btn-line-text">No</span> <span class="btn-line-text">No</span>
</button> </button>
<button class="btn-line accept" type="button" data-dismiss="modal"> <button class="btn-line accept" type="button">
<span class="btn-line-text">Yes</span> <span class="btn-line-text">Yes</span>
</button> </button>
<% end %> <% end %>

@ -0,0 +1 @@
<svg height="12pt" viewBox="0 0 12 12" width="12pt" xmlns="http://www.w3.org/2000/svg"><path d="m9.992188 1.496094c-.019532 0-.039063 0-.058594.003906h-2.433594c-.179688-.003906-.347656.09375-.4375.246094-.09375.15625-.09375.351562 0 .507812.089844.152344.257812.25.4375.246094h1.292969l-4.644531 4.648438c-.132813.125-.183594.308593-.140626.484374.046876.175782.183594.3125.359376.359376.175781.042968.359374-.007813.488281-.136719l4.644531-4.648438v1.292969c-.003906.179688.09375.347656.246094.4375.15625.09375.351562.09375.507812 0 .152344-.089844.25-.257812.246094-.4375v-2.4375c.019531-.144531-.023438-.292969-.125-.402344-.097656-.109375-.238281-.167968-.382812-.164062zm-7.492188.003906c-.546875 0-1 .453125-1 1v7c0 .546875.453125 1 1 1h7c.546875 0 1-.453125 1-1v-3c.003906-.179688-.09375-.347656-.246094-.4375-.15625-.09375-.351562-.09375-.507812 0-.152344.089844-.25.257812-.246094.4375v3h-7v-7h3c.179688.003906.347656-.09375.4375-.246094.09375-.15625.09375-.351562 0-.507812-.089844-.152344-.257812-.25-.4375-.246094zm0 0" fill="#333"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -55,12 +55,16 @@
class: "dropdown-item #{tab_status("txs", @conn.request_path)}", class: "dropdown-item #{tab_status("txs", @conn.request_path)}",
to: transaction_path(@conn, :index) to: transaction_path(@conn, :index)
) %> ) %>
<%= link( <% json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)%>
gettext("Pending"), <% variant = Keyword.fetch!(json_rpc_named_arguments, :variant) %>
class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}", <%= if variant !== EthereumJSONRPC.Besu do %>
"data-test": "pending_transactions_link", <%= link(
to: pending_transaction_path(@conn, :index) gettext("Pending"),
) %> class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
"data-test": "pending_transactions_link",
to: pending_transaction_path(@conn, :index)
) %>
<% end %>
</div> </div>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -99,6 +103,25 @@
</div> </div>
</li> </li>
<% end %> <% end %>
<%= if Application.get_env(:block_scout_web, :apps_menu) == true do %>
<li class="nav-item dropdown">
<a href="#" role="button" id="navbarAppsDropdown" class="nav-link topnav-nav-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_apps_icon.html" %>
</span>
<%= gettext("Apps") %>
</a>
<div class="dropdown-menu" aria-labeledby="navbarAppsDropdown">
<%= for %{url: url, title: title} <- external_apps_list() do %>
<a href="<%= url%>" class="dropdown-item" target="_blank"><%= title %>
<span class="external-link-icon">
<%= render BlockScoutWeb.IconsView, "_external_link.html" %>
</span>
</a>
<% end %>
</div>
</li>
<% end %>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link topnav-nav-link active-icon <%= if dropdown_nets() != [], do: "dropdown-toggle js-show-network-selector" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link topnav-nav-link active-icon <%= if dropdown_nets() != [], do: "dropdown-toggle js-show-network-selector" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon"> <span class="nav-link-icon">

@ -86,6 +86,9 @@
@view_module != Elixir.BlockScoutWeb.AddressContractView && @view_module != Elixir.BlockScoutWeb.AddressContractView &&
@view_module != Elixir.BlockScoutWeb.AddressContractVerificationView && @view_module != Elixir.BlockScoutWeb.AddressContractVerificationView &&
@view_module != Elixir.BlockScoutWeb.AddressReadContractView && @view_module != Elixir.BlockScoutWeb.AddressReadContractView &&
@view_module != Elixir.BlockScoutWeb.AddressReadProxyView &&
@view_module != Elixir.BlockScoutWeb.AddressWriteContractView &&
@view_module != Elixir.BlockScoutWeb.AddressWriteProxyView &&
@view_module != Elixir.BlockScoutWeb.Tokens.TransferView && @view_module != Elixir.BlockScoutWeb.Tokens.TransferView &&
@view_module != Elixir.BlockScoutWeb.Tokens.ReadContractView && @view_module != Elixir.BlockScoutWeb.Tokens.ReadContractView &&
@view_module != Elixir.BlockScoutWeb.Tokens.HolderView && @view_module != Elixir.BlockScoutWeb.Tokens.HolderView &&

@ -0,0 +1,37 @@
<div class="table-responsive text-center">
<table style="color: black;table-layout: fixed;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col" style="width: 110px;"><%= gettext "Name" %></th>
<th scope="col" style="width: 100px;"><%= gettext "Type" %></th>
<th scope="col" style="width: 75px;"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- @mapping do %>
<tr>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td align=left>
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
style="float: left;height: 20px;"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="25" height="25">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
<pre class="transaction-input-text pre-wrap"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div>

@ -1,3 +1,14 @@
<%= if @action== "write" do %>
<button connect-metamask class="button btn-line">Connect to Metamask</button>
<% end %>
<%= if @contract_type == "proxy" do %>
<div class="implementation-container">
<h2 class="implementation-title">Implementation address: </h2><h3 class="implementation-value"><%= link(
@implementation_address,
to: address_path(@conn, :show, @implementation_address)
) %></h3>
</div>
<% end %>
<%= for {function, counter} <- Enum.with_index(@read_only_functions, 1) do %> <%= for {function, counter} <- Enum.with_index(@read_only_functions, 1) do %>
<div class="d-flex py-2 border-bottom" data-function> <div class="d-flex py-2 border-bottom" data-function>
<div class="py-2 pr-2 text-nowrap"> <div class="py-2 pr-2 text-nowrap">
@ -8,51 +19,69 @@
&#8594; &#8594;
</div> </div>
<%= if queryable?(function["inputs"]) do %> <%= if queryable?(function["inputs"]) || writeable?(function) do %>
<div style="width: 100%; overflow: hidden;"> <div style="width: 100%; overflow: hidden;">
<form class="form-inline" data-function-form data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>"> <%=
for status <- ["error", "warning", "success", "question"] do
render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status
end
%>
<%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %>
<form class="form-inline" data-function-form data-action="<%= if writeable?(function), do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= @contract_abi %>" data-implementation-abi="<%= @implementation_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>">
<input type="hidden" name="function_name" value='<%= function["name"] %>' /> <input type="hidden" name="function_name" value='<%= function["name"] %>' />
<%= for input <- function["inputs"] do %> <%= if queryable?(function["inputs"]) do %>
<%= for input <- function["inputs"] do %>
<div class="form-group pr-2">
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm mt-2" placeholder='<%= input["name"] %>(<%= input["type"] %>)' />
</div>
<% end %>
<% end %>
<%= if payable?(function) do %>
<div class="form-group pr-2"> <div class="form-group pr-2">
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm mt-2" placeholder='<%= input["name"] %>(<%= input["type"] %>)' /> <input type="text" name="function_input" tx-value class="form-control form-control-sm address-input-sm mt-2" placeholder='value(<%= gettext("ETH")%>)' />
</div> </div>
<% end %> <% end %>
<input type="submit" value='<%= gettext("Query")%>' class="button btn-line button-xs py-0 mt-2" style="padding: 6px 8px!important;height: 26px;font-size: 11px;" /> <input type="submit" value='<%= if writeable?(function), do: gettext("Write"), else: gettext("Query")%>' class="button btn-line button-xs py-0 mt-2" style="padding: 6px 8px!important;height: 26px;font-size: 11px;" />
</form> </form>
<div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'> <%= if outputs?(function["outputs"]) do %>
<%= if (queryable?(function["inputs"])), do: raw "&#8627;" %> <div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'>
<%= if (queryable?(function["inputs"])), do: raw "&#8627;" %>
<%= for output <- function["outputs"] do %> <%= for output <- function["outputs"] do %>
<%= output["type"] %> <%= output["type"] %>
<% end %> <% end %>
</div> </div>
<% end %>
<div data-function-response></div> <div data-function-response></div>
</div> </div>
<% else %> <% else %>
<span class="py-2"> <span class="py-2 word-break-all">
<%= for output <- function["outputs"] do %> <%= if outputs?(function["outputs"]) do %>
<%= if address?(output["type"]) do %> <%= for output <- function["outputs"] do %>
<%= link( <%= if address?(output["type"]) do %>
output["value"], <%= link(
to: address_path(@conn, :show, output["value"]) output["value"],
) %> to: address_path(@conn, :show, output["value"])
<% else %> ) %>
<%= if output["type"] == "uint256" do %> <% else %>
<div data-wei-ether-converter> <%= if output["type"] == "uint256" do %>
<span data-conversion-unit><%= output["value"] %></span> <div data-wei-ether-converter>
<span class="py-2 px-2"> <span data-conversion-unit><%= output["value"] %></span>
<input class="wei-ether" type="checkbox" autocomplete="off"> <span class="py-2 px-2">
<span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span> <input class="wei-ether" type="checkbox" autocomplete="off">
<span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span> <span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span>
</span> <span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span>
</div> </span>
<% else %> </div>
<%= values(output["value"], output["type"]) %> <% else %>
<% end %> <%= values(output["value"], output["type"]) %>
<% end %>
<% end %>
<% end %> <% end %>
<% end %> <% end %>
</span> </span>

@ -0,0 +1,23 @@
<div id="pending-contract-write" class="modal modal-fullwidth-xs fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-stake" role="document">
<div class="modal-content">
<%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %>
<div class="modal-stake-two-cols">
<div class="modal-header">
<div class="modal-header-group">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<h5 class="modal-title centered"><%= gettext("Waiting for transaction's confirmation...") %></h5>
</div>
</div>
<div class="modal-body">
<form>
<p class="form-p modal-status-text modal-message"></p>
</form>
</div>
</div>
</div>
</div>
</div>

@ -11,7 +11,7 @@
<div class="card"> <div class="card">
<%= render OverviewView, "_tabs.html", assigns %> <%= render OverviewView, "_tabs.html", assigns %>
<!-- loaded via AJAX --> <!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= Address.checksum(@token.contract_address.hash) %>" data-url="<%= smart_contract_path(@conn, :index) %>"> <div class="card-body" data-smart-contract-functions data-hash="<%= Address.checksum(@token.contract_address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div class="tile tile-muted text-center"> <div class="tile tile-muted text-center">
<span class="loading-spinner-small mr-2"> <span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>

@ -15,41 +15,39 @@
<div class="table-responsive text-center"> <div class="table-responsive text-center">
<table style="color: black; table-layout: fixed;" summary="<%= gettext "Transaction Inputs" %>" class="table thead-light table-bordered"> <table style="color: black; table-layout: fixed;" summary="<%= gettext "Transaction Inputs" %>" class="table thead-light table-bordered">
<tr> <tr>
<th scope="col" style="width: 60px;"></th>
<th scope="col" style="width: 10%;"><%= gettext "Name" %></th> <th scope="col" style="width: 10%;"><%= gettext "Name" %></th>
<th scope="col" style="width: 10%;"><%= gettext "Type" %></th> <th scope="col" style="width: 10%;"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Data" %></th> <th scope="col"><%= gettext "Data" %></th>
<tr> <tr>
<%= for {name, type, value} <- @mapping do %> <%= for {name, type, value} <- @mapping do %>
<tr> <tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text='<%= copy_text %>'
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td> <td><%= name %></td>
<td><%= type %></td> <td><%= type %></td>
<td> <td align=left>
<%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %> <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
<% :error -> %> <% :error -> %>
<div class="alert alert-danger"> <div class="alert alert-danger">
<%= gettext "Error rendering value" %> <%= gettext "Error rendering value" %>
</div> </div>
<% value -> %> <% _value -> %>
<pre class="transaction-input-text tile pre-wrap"><code><%= value %></code></pre> <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text='<%= copy_text %>'
data-placement="top"
data-toggle="tooltip"
style="float: left;height: 20px;"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="25" height="25">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
<pre class="transaction-input-text pre-wrap" style="margin-bottom: 0px;"><code style="line-height: 25px;"><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
<% end %> <% end %>
</td> </td>
</tr> </tr>

@ -30,4 +30,5 @@
<a class="error-btn btn-line" href="/"><%= gettext("Back Home") %></a> <a class="error-btn btn-line" href="/"><%= gettext("Back Home") %></a>
</div> </div>
</div> </div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/transaction.js") %>"></script>
</section> </section>

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

@ -49,45 +49,7 @@
<td colspan="3"><code><%= text %></code></td> <td colspan="3"><code><%= text %></code></td>
</tr> </tr>
</table> </table>
<div class="table-responsive text-center"> <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<table style="color: black;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td>
<pre class="transaction-input-text tile"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div>
<% {:error, :contract_not_verified, results} -> %> <% {:error, :contract_not_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %> <%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt> <dt class="col-md-2"><%= gettext "Decoded" %></dt>
@ -102,45 +64,7 @@
<td colspan="3"><code><%= text %></code></td> <td colspan="3"><code><%= text %></code></td>
</tr> </tr>
</table> </table>
<div class="table-responsive text-center"> <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<table style="color: black;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td>
<pre class="transaction-input-text tile"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div>
<% end %> <% end %>
<% _ -> %> <% _ -> %>
<%= nil %> <%= nil %>

@ -4,7 +4,22 @@
<div class="card"> <div class="card">
<%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %> <%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %>
<div class="card-body"> <div class="card-body">
<h2 class="card-title"><%= gettext "Raw Trace" %></h2> <h2 class="card-title"><%= gettext "Raw Trace" %>
<span
aria-label="Copy Value"
class="btn-copy-icon tx-raw-input transaction-input"
id="tx-raw-input"
data-clipboard-text="<%= for {line, _} <- raw_traces_with_lines(@internal_transactions), do: line %>"
data-placement="top"
data-toggle="tooltip"
title='<%= gettext("Copy Raw Trace") %>'
style="float: right;"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
</h2>
<%= if Enum.count(@internal_transactions) > 0 do %> <%= if Enum.count(@internal_transactions) > 0 do %>
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="json "><%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre> <pre class="pre-scrollable line-numbers" data-activate-highlight><code class="json "><%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
<% else %> <% else %>

@ -1,7 +1,13 @@
defmodule BlockScoutWeb.AddressReadContractView do defmodule BlockScoutWeb.AddressReadContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
def queryable?(inputs), do: Enum.any?(inputs) def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
def address?(type), do: type == "address" def address?(type), do: type == "address"
end end

@ -0,0 +1,13 @@
defmodule BlockScoutWeb.AddressReadProxyView do
use BlockScoutWeb, :view
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
def address?(type), do: type == "address"
end

@ -8,6 +8,7 @@ defmodule BlockScoutWeb.AddressView do
alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei} alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei}
alias Explorer.Chain.Block.Reward alias Explorer.Chain.Block.Reward
alias Explorer.ExchangeRates.Token, as: TokenExchangeRate alias Explorer.ExchangeRates.Token, as: TokenExchangeRate
alias Explorer.SmartContract.Writer
@dialyzer :no_match @dialyzer :no_match
@ -18,6 +19,9 @@ defmodule BlockScoutWeb.AddressView do
"internal_transactions", "internal_transactions",
"token_transfers", "token_transfers",
"read_contract", "read_contract",
"read_proxy",
"write_contract",
"write_proxy",
"tokens", "tokens",
"transactions", "transactions",
"validations" "validations"
@ -228,6 +232,21 @@ 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 smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
Chain.is_proxy_contract?(address.smart_contract.abi)
end
def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false
def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do
Enum.any?(
address.smart_contract.abi,
&Writer.write_function?(&1)
)
end
def smart_contract_with_write_functions?(%Address{smart_contract: nil}), do: false
def has_decompiled_code?(address) do def has_decompiled_code?(address) do
address.has_decompiled_code? || address.has_decompiled_code? ||
(Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0) (Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0)
@ -326,6 +345,9 @@ defmodule BlockScoutWeb.AddressView do
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(["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(["read_proxy"]), do: gettext("Read Proxy")
defp tab_name(["write_contract"]), do: gettext("Write Contract")
defp tab_name(["write_proxy"]), do: gettext("Write Proxy")
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")
defp tab_name(["logs"]), do: gettext("Logs") defp tab_name(["logs"]), do: gettext("Logs")

@ -0,0 +1,13 @@
defmodule BlockScoutWeb.AddressWriteContractView do
use BlockScoutWeb, :view
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
def address?(type), do: type == "address"
end

@ -0,0 +1,13 @@
defmodule BlockScoutWeb.AddressWriteProxyView do
use BlockScoutWeb, :view
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
def address?(type), do: type == "address"
end

@ -22,6 +22,11 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
end end
def render("pendingtxlist.json", %{transactions: transactions}) do
data = Enum.map(transactions, &prepare_pending_transaction/1)
RPCView.render("show.json", data: data)
end
def render("txlist.json", %{transactions: transactions}) do def render("txlist.json", %{transactions: transactions}) do
data = Enum.map(transactions, &prepare_transaction/1) data = Enum.map(transactions, &prepare_transaction/1)
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
@ -79,6 +84,22 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
} }
end end
defp prepare_pending_transaction(transaction) do
%{
"hash" => "#{transaction.hash}",
"nonce" => "#{transaction.nonce}",
"from" => "#{transaction.from_address_hash}",
"to" => "#{transaction.to_address_hash}",
"value" => "#{transaction.value.value}",
"gas" => "#{transaction.gas}",
"gasPrice" => "#{transaction.gas_price.value}",
"input" => "#{transaction.input}",
"contractAddress" => "#{transaction.created_contract_address_hash}",
"cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
"gasUsed" => "#{transaction.gas_used}"
}
end
defp prepare_transaction(transaction) do defp prepare_transaction(transaction) do
%{ %{
"blockNumber" => "#{transaction.block_number}", "blockNumber" => "#{transaction.block_number}",

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

@ -17,12 +17,7 @@ defmodule BlockScoutWeb.LayoutView do
}, },
%{ %{
title: "xDai Chain", title: "xDai Chain",
url: "https://blockscout.com/poa/dai" url: "https://blockscout.com/poa/xdai"
},
%{
title: "Kovan Testnet",
url: "https://blockscout.com/eth/kovan",
test_net?: true
}, },
%{ %{
title: "Ethereum Classic", title: "Ethereum Classic",
@ -221,6 +216,21 @@ defmodule BlockScoutWeb.LayoutView do
end end
end end
def external_apps_list do
if Application.get_env(:block_scout_web, :external_apps) do
try do
:block_scout_web
|> Application.get_env(:external_apps)
|> Parser.parse!(%{keys: :atoms!})
rescue
_ ->
[]
end
else
[]
end
end
defp validate_url(url) when is_binary(url) do defp validate_url(url) when is_binary(url) do
case URI.parse(url) do case URI.parse(url) do
%URI{host: nil} -> :error %URI{host: nil} -> :error

@ -0,0 +1,3 @@
defmodule BlockScoutWeb.LogView do
use BlockScoutWeb, :view
end

@ -1,7 +1,35 @@
defmodule BlockScoutWeb.SmartContractView do defmodule BlockScoutWeb.SmartContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
def queryable?(inputs), do: Enum.any?(inputs) def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
def queryable?(inputs) when is_nil(inputs), do: false
def writeable?(function) when not is_nil(function),
do:
!constructor?(function) && !event?(function) &&
(payable?(function) || nonpayable?(function))
def writeable?(function) when is_nil(function), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
defp event?(function), do: function["type"] == "event"
defp constructor?(function), do: function["type"] == "constructor"
def payable?(function), do: function["stateMutability"] == "payable" || function["payable"]
def nonpayable?(function) do
if function["type"] do
function["stateMutability"] == "nonpayable" ||
(!function["payable"] && !function["constant"] && !function["stateMutability"])
else
false
end
end
def address?(type), do: type in ["address", "address payable"] def address?(type), do: type in ["address", "address payable"]

@ -237,6 +237,10 @@ defmodule BlockScoutWeb.TransactionView do
Chain.transaction_to_status(transaction) Chain.transaction_to_status(transaction)
end end
def transaction_revert_reason(transaction) do
Chain.transaction_to_revert_reason(transaction)
end
def empty_exchange_rate?(exchange_rate) do def empty_exchange_rate?(exchange_rate) do
Token.null?(exchange_rate) Token.null?(exchange_rate)
end end

@ -125,6 +125,27 @@ defmodule BlockScoutWeb.WebRouter do
as: :read_contract as: :read_contract
) )
resources(
"/read_proxy",
AddressReadProxyController,
only: [:index, :show],
as: :read_proxy
)
resources(
"/write_contract",
AddressWriteContractController,
only: [:index, :show],
as: :write_contract
)
resources(
"/write_proxy",
AddressWriteProxyController,
only: [:index, :show],
as: :write_proxy
)
resources( resources(
"/token_transfers", "/token_transfers",
AddressTokenTransferController, AddressTokenTransferController,

@ -60,13 +60,13 @@ defmodule BlockScoutWeb.Mixfile do
defp deps do defp deps do
[ [
# GraphQL toolkit # GraphQL toolkit
{:absinthe, "~> 1.5.0"}, {:absinthe, "~> 1.5.2"},
# Integrates Absinthe subscriptions with Phoenix # Integrates Absinthe subscriptions with Phoenix
{:absinthe_phoenix, "~> 2.0.0"}, {:absinthe_phoenix, "~> 2.0.0"},
# Plug support for Absinthe # Plug support for Absinthe
{:absinthe_plug, "~> 1.5.0"}, {:absinthe_plug, "~> 1.5.0"},
# Absinthe support for the Relay framework # Absinthe support for the Relay framework
{:absinthe_relay, "~> 1.5.0"}, {:absinthe_relay, "~> 1.5"},
{:bypass, "~> 1.0", only: :test}, {:bypass, "~> 1.0", only: :test},
# To add (CORS)(https://www.w3.org/TR/cors/) # To add (CORS)(https://www.w3.org/TR/cors/)
{:cors_plug, "~> 2.0"}, {:cors_plug, "~> 2.0"},
@ -99,8 +99,8 @@ defmodule BlockScoutWeb.Mixfile do
{:logger_file_backend, "~> 0.0.10"}, {:logger_file_backend, "~> 0.0.10"},
{:math, "~> 0.3.0"}, {:math, "~> 0.3.0"},
{:mock, "~> 0.3.0", only: [:test], runtime: false}, {:mock, "~> 0.3.0", only: [:test], runtime: false},
{:phoenix, "== 1.5.3"}, {:phoenix, "== 1.5.4"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.1"},
{:phoenix_html, "~> 2.10"}, {:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.2", only: [:dev]}, {:phoenix_live_reload, "~> 1.2", only: [:dev]},
{:phoenix_pubsub, "~> 2.0"}, {:phoenix_pubsub, "~> 2.0"},

@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:196 #: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer" msgid " Token Transfer"
msgstr "" msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:247 #: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:81 #: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs" msgid "APIs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:71 #: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts" msgid "Accounts"
msgstr "" msgstr ""
@ -124,7 +124,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/_logs.html.eex:19 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
#: lib/block_scout_web/views/address_view.ex:100 #: lib/block_scout_web/views/address_view.ex:104
msgid "Address" msgid "Address"
msgstr "" msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:72 #: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations" msgid "Block Confirmations"
msgstr "" msgstr ""
@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58 #: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number" msgid "Block Number"
msgstr "" msgstr ""
@ -338,14 +338,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:98 #: lib/block_scout_web/views/address_view.ex:102
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:38 #: lib/block_scout_web/views/address_view.ex:42
#: lib/block_scout_web/views/address_view.ex:72 #: lib/block_scout_web/views/address_view.ex:76
msgid "Contract Address Pending" msgid "Contract Address Pending"
msgstr "" msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:336 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:333 #: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -448,11 +448,8 @@ msgid "Difficulty"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:66 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:119 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:69
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:122
msgid "Copy Value" msgid "Copy Value"
msgstr "" msgstr ""
@ -478,23 +475,20 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:56 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:100
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:109 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:175 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:103
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:112
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:179
msgid "Data" msgid "Data"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:31
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:37 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:37
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:90 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:52
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:93 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55
msgid "Decoded" msgid "Decoded"
msgstr "" msgstr ""
@ -568,7 +562,8 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:50 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:78
msgid "ETH" msgid "ETH"
msgstr "" msgstr ""
@ -604,7 +599,7 @@ msgid "Enter the Solidity Contract Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:30
msgid "Error rendering value" msgid "Error rendering value"
msgstr "" msgstr ""
@ -621,12 +616,12 @@ msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:251 #: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:249 #: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
@ -636,7 +631,7 @@ msgid "Error: Could not determine contract creator."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 #: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC" msgid "Eth RPC"
msgstr "" msgstr ""
@ -646,8 +641,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32 #: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29 #: lib/block_scout_web/templates/transaction/_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -666,7 +661,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:73
#: lib/block_scout_web/templates/transaction/overview.html.eex:79 #: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce" msgid "Nonce"
msgstr "" msgstr ""
@ -737,7 +732,7 @@ msgid "Github"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 #: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL" msgid "GraphQL"
msgstr "" msgstr ""
@ -753,8 +748,8 @@ msgid "Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115 #: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:119 #: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
@ -776,7 +771,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:248 #: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -789,7 +784,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/transaction/_tile.html.eex:32
#: lib/block_scout_web/templates/transaction/overview.html.eex:84 #: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee" msgid "TX Fee"
msgstr "" msgstr ""
@ -891,7 +886,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:329 #: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer" msgid "Token Transfer"
msgstr "" msgstr ""
@ -903,10 +898,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:325 #: lib/block_scout_web/views/address_view.ex:344
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35 #: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:390 #: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers" msgid "Token Transfers"
msgstr "" msgstr ""
@ -922,7 +917,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
#: lib/block_scout_web/views/transaction_view.ex:339 #: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -932,10 +927,7 @@ msgid "If you have just submitted this transaction please wait for at least 30 s
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:55 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:108
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:111
msgid "Indexed?" msgid "Indexed?"
msgstr "" msgstr ""
@ -1005,12 +997,15 @@ msgid "License ID"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283 #: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
#: lib/block_scout_web/templates/address_write_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1021,10 +1016,7 @@ msgid "Loading...."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:103
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:106
msgid "Log Data" msgid "Log Data"
msgstr "" msgstr ""
@ -1041,7 +1033,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52 #: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30 #: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:138 #: lib/block_scout_web/views/address_view.ex:142
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
@ -1085,13 +1077,10 @@ msgid "Must be set to:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:106
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:109
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -1158,9 +1147,9 @@ msgid "Parent Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 #: lib/block_scout_web/templates/layout/_topnav.html.eex:62
#: lib/block_scout_web/views/transaction_view.ex:246 #: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:280 #: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -1191,24 +1180,24 @@ msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:22 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Query" msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90 #: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC" msgid "RPC"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109 #: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input" msgid "Raw Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:393 #: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace" msgid "Raw Trace"
msgstr "" msgstr ""
@ -1254,14 +1243,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14 #: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:141 #: lib/block_scout_web/templates/layout/_topnav.html.eex:164
#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 #: lib/block_scout_web/templates/layout/_topnav.html.eex:181
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:135 #: lib/block_scout_web/templates/layout/_topnav.html.eex:158
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139 #: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search by address, token symbol name, transaction hash, or block number" msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr "" msgstr ""
@ -1451,8 +1440,8 @@ msgid "Topic"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:145 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:70
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:149 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73
msgid "Topics" msgid "Topics"
msgstr "" msgstr ""
@ -1488,7 +1477,7 @@ msgid "Transaction Inputs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:101 #: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed" msgid "Transaction Speed"
msgstr "" msgstr ""
@ -1509,16 +1498,13 @@ msgid "Twitter"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:54 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122 #: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8" msgid "UTF-8"
msgstr "" msgstr ""
@ -1544,7 +1530,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277 #: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1574,8 +1560,8 @@ msgid "Validator Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1645,7 +1631,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:49 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77
msgid "WEI" msgid "WEI"
msgstr "" msgstr ""
@ -1752,7 +1738,7 @@ msgid "Decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -1772,7 +1758,7 @@ msgid "Loading chart"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:189 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
msgid "Log Index" msgid "Log Index"
msgstr "" msgstr ""
@ -1799,15 +1785,15 @@ msgid "Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134 #: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:147 #: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input" msgid "Copy Txn Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:330 #: lib/block_scout_web/views/address_view.ex:352
msgid "Blocks Validated" msgid "Blocks Validated"
msgstr "" msgstr ""
@ -1817,18 +1803,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:326 #: lib/block_scout_web/views/address_view.ex:345
msgid "Code" msgid "Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:329 #: lib/block_scout_web/views/address_view.ex:351
msgid "Coin Balance History" msgid "Coin Balance History"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_view.ex:327 #: lib/block_scout_web/views/address_view.ex:346
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
@ -1837,8 +1823,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324 #: lib/block_scout_web/views/address_view.ex:343
#: lib/block_scout_web/views/transaction_view.ex:391 #: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -1847,15 +1833,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331 #: lib/block_scout_web/views/address_view.ex:353
#: lib/block_scout_web/views/transaction_view.ex:392 #: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62 #: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:328 #: lib/block_scout_web/views/address_view.ex:347
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -1864,7 +1850,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:322 #: lib/block_scout_web/views/address_view.ex:341
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
@ -1876,31 +1862,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184 #: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50 #: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:323 #: lib/block_scout_web/views/address_view.ex:342
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:232 #: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning" msgid " Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
#: lib/block_scout_web/views/transaction_view.ex:328 #: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning" msgid "Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214 #: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting" msgid " Token Minting"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:327 #: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting" msgid "Token Minting"
msgstr "" msgstr ""
@ -1908,3 +1894,47 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62 #: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward" msgid "Chore Reward"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:69
#: lib/block_scout_web/views/address_view.ex:348
msgid "Read Proxy"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/views/address_view.ex:349
msgid "Write Contract"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:12
msgid "Waiting for transaction's confirmation..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Write"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:83
#: lib/block_scout_web/views/address_view.ex:350
msgid "Write Proxy"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
msgid "Apps"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15
msgid "Copy Raw Trace"
msgstr ""

@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:196 #: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer" msgid " Token Transfer"
msgstr "" msgstr ""
@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:247 #: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)" msgid "(Awaiting internal transactions for status)"
msgstr "" msgstr ""
@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:81 #: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs" msgid "APIs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:71 #: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts" msgid "Accounts"
msgstr "" msgstr ""
@ -124,7 +124,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/_logs.html.eex:19 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
#: lib/block_scout_web/views/address_view.ex:100 #: lib/block_scout_web/views/address_view.ex:104
msgid "Address" msgid "Address"
msgstr "" msgstr ""
@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:72 #: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations" msgid "Block Confirmations"
msgstr "" msgstr ""
@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58 #: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number" msgid "Block Number"
msgstr "" msgstr ""
@ -338,14 +338,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:98 #: lib/block_scout_web/views/address_view.ex:102
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:38 #: lib/block_scout_web/views/address_view.ex:42
#: lib/block_scout_web/views/address_view.ex:72 #: lib/block_scout_web/views/address_view.ex:76
msgid "Contract Address Pending" msgid "Contract Address Pending"
msgstr "" msgstr ""
@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:336 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:333 #: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -448,11 +448,8 @@ msgid "Difficulty"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:66 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:119 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:69
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:122
msgid "Copy Value" msgid "Copy Value"
msgstr "" msgstr ""
@ -478,23 +475,20 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:56 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:100
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:109 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:175 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:103
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:112
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:179
msgid "Data" msgid "Data"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:31
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:37 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:37
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:90 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:52
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:93 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55
msgid "Decoded" msgid "Decoded"
msgstr "" msgstr ""
@ -568,7 +562,8 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:50 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:78
msgid "ETH" msgid "ETH"
msgstr "" msgstr ""
@ -604,7 +599,7 @@ msgid "Enter the Solidity Contract Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:30
msgid "Error rendering value" msgid "Error rendering value"
msgstr "" msgstr ""
@ -621,12 +616,12 @@ msgid "Error trying to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:251 #: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}" msgid "Error: %{reason}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:249 #: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)" msgid "Error: (Awaiting internal transactions for reason)"
msgstr "" msgstr ""
@ -636,7 +631,7 @@ msgid "Error: Could not determine contract creator."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 #: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC" msgid "Eth RPC"
msgstr "" msgstr ""
@ -646,8 +641,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32 #: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29 #: lib/block_scout_web/templates/transaction/_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -666,7 +661,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:73
#: lib/block_scout_web/templates/transaction/overview.html.eex:79 #: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce" msgid "Nonce"
msgstr "" msgstr ""
@ -737,7 +732,7 @@ msgid "Github"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 #: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL" msgid "GraphQL"
msgstr "" msgstr ""
@ -753,8 +748,8 @@ msgid "Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:115 #: lib/block_scout_web/templates/transaction/overview.html.eex:123
#: lib/block_scout_web/templates/transaction/overview.html.eex:119 #: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
@ -776,7 +771,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:248 #: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -789,7 +784,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/transaction/_tile.html.eex:32
#: lib/block_scout_web/templates/transaction/overview.html.eex:84 #: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee" msgid "TX Fee"
msgstr "" msgstr ""
@ -891,7 +886,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/views/transaction_view.ex:329 #: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer" msgid "Token Transfer"
msgstr "" msgstr ""
@ -903,10 +898,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:325 #: lib/block_scout_web/views/address_view.ex:344
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35 #: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:390 #: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers" msgid "Token Transfers"
msgstr "" msgstr ""
@ -922,7 +917,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
#: lib/block_scout_web/views/transaction_view.ex:339 #: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -932,10 +927,7 @@ msgid "If you have just submitted this transaction please wait for at least 30 s
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:55 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:108
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:111
msgid "Indexed?" msgid "Indexed?"
msgstr "" msgstr ""
@ -1005,12 +997,15 @@ msgid "License ID"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:283 #: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
#: lib/block_scout_web/templates/address_write_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1021,10 +1016,7 @@ msgid "Loading...."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:103
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:106
msgid "Log Data" msgid "Log Data"
msgstr "" msgstr ""
@ -1041,7 +1033,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52 #: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30 #: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:138 #: lib/block_scout_web/views/address_view.ex:142
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
@ -1085,13 +1077,10 @@ msgid "Must be set to:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:106
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:109
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -1158,9 +1147,9 @@ msgid "Parent Hash"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 #: lib/block_scout_web/templates/layout/_topnav.html.eex:62
#: lib/block_scout_web/views/transaction_view.ex:246 #: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:280 #: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -1191,24 +1180,24 @@ msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:22 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Query" msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:90 #: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC" msgid "RPC"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109 #: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input" msgid "Raw Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:393 #: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace" msgid "Raw Trace"
msgstr "" msgstr ""
@ -1254,14 +1243,14 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14 #: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:141 #: lib/block_scout_web/templates/layout/_topnav.html.eex:164
#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 #: lib/block_scout_web/templates/layout/_topnav.html.eex:181
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:135 #: lib/block_scout_web/templates/layout/_topnav.html.eex:158
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139 #: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search by address, token symbol name, transaction hash, or block number" msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr "" msgstr ""
@ -1451,8 +1440,8 @@ msgid "Topic"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:145 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:70
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:149 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73
msgid "Topics" msgid "Topics"
msgstr "" msgstr ""
@ -1488,7 +1477,7 @@ msgid "Transaction Inputs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:101 #: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed" msgid "Transaction Speed"
msgstr "" msgstr ""
@ -1509,16 +1498,13 @@ msgid "Twitter"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:54 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:122 #: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8" msgid "UTF-8"
msgstr "" msgstr ""
@ -1544,7 +1530,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:277 #: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -1574,8 +1560,8 @@ msgid "Validator Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:181 #: lib/block_scout_web/templates/transaction/overview.html.eex:189
#: lib/block_scout_web/templates/transaction/overview.html.eex:255 #: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -1645,7 +1631,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:49 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77
msgid "WEI" msgid "WEI"
msgstr "" msgstr ""
@ -1752,7 +1738,7 @@ msgid "Decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -1772,7 +1758,7 @@ msgid "Loading chart"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:189 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
msgid "Log Index" msgid "Log Index"
msgstr "" msgstr ""
@ -1799,15 +1785,15 @@ msgid "Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:134 #: lib/block_scout_web/templates/transaction/overview.html.eex:142
#: lib/block_scout_web/templates/transaction/overview.html.eex:147 #: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input" msgid "Copy Txn Input"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:330 #: lib/block_scout_web/views/address_view.ex:352
msgid "Blocks Validated" msgid "Blocks Validated"
msgstr "" msgstr ""
@ -1817,18 +1803,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:326 #: lib/block_scout_web/views/address_view.ex:345
msgid "Code" msgid "Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:329 #: lib/block_scout_web/views/address_view.ex:351
msgid "Coin Balance History" msgid "Coin Balance History"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_view.ex:327 #: lib/block_scout_web/views/address_view.ex:346
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
@ -1837,8 +1823,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324 #: lib/block_scout_web/views/address_view.ex:343
#: lib/block_scout_web/views/transaction_view.ex:391 #: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -1847,15 +1833,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331 #: lib/block_scout_web/views/address_view.ex:353
#: lib/block_scout_web/views/transaction_view.ex:392 #: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62 #: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:328 #: lib/block_scout_web/views/address_view.ex:347
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -1864,7 +1850,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14 #: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:322 #: lib/block_scout_web/views/address_view.ex:341
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
@ -1876,31 +1862,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184 #: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50 #: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:323 #: lib/block_scout_web/views/address_view.ex:342
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:232 #: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning" msgid " Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
#: lib/block_scout_web/views/transaction_view.ex:328 #: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning" msgid "Token Burning"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214 #: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting" msgid " Token Minting"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:327 #: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting" msgid "Token Minting"
msgstr "" msgstr ""
@ -1908,3 +1894,47 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62 #: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward" msgid "Chore Reward"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:69
#: lib/block_scout_web/views/address_view.ex:348
msgid "Read Proxy"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:76
#: lib/block_scout_web/views/address_view.ex:349
msgid "Write Contract"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:12
msgid "Waiting for transaction's confirmation..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Write"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:83
#: lib/block_scout_web/views/address_view.ex:350
msgid "Write Proxy"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
msgid "Apps"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15
msgid "Copy Raw Trace"
msgstr ""

@ -0,0 +1,79 @@
defmodule BlockScoutWeb.AddressReadProxyControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -0,0 +1,81 @@
defmodule BlockScoutWeb.AddressWriteContractControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -0,0 +1,81 @@
defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -1419,6 +1419,332 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end end
end end
describe "pendingtxlist" do
test "with missing address hash", %{conn: conn} do
params = %{
"module" => "account",
"action" => "pendingtxlist"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "'address' is required"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with an invalid address hash", %{conn: conn} do
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "badhash"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "Invalid address format"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with an address that doesn't exist", %{conn: conn} do
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == []
assert response["status"] == "0"
assert response["message"] == "No transactions found"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with a valid address", %{conn: conn} do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}"
}
expected_result = [
%{
"hash" => "#{transaction.hash}",
"nonce" => "#{transaction.nonce}",
"from" => "#{transaction.from_address_hash}",
"to" => "#{transaction.to_address_hash}",
"value" => "#{transaction.value.value}",
"gas" => "#{transaction.gas}",
"gasPrice" => "#{transaction.gas_price.value}",
"input" => "#{transaction.input}",
"contractAddress" => "#{transaction.created_contract_address_hash}",
"cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
"gasUsed" => "#{transaction.gas_used}"
}
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with address with multiple transactions", %{conn: conn} do
address1 = insert(:address)
address2 = insert(:address)
transactions =
3
|> insert_list(:transaction, from_address: address1)
:transaction
|> insert(from_address: address2)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address1.hash}"
}
expected_transaction_hashes = Enum.map(transactions, &"#{&1.hash}")
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 3
for returned_transaction <- response["result"] do
assert returned_transaction["hash"] in expected_transaction_hashes
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with valid pagination params", %{conn: conn} do
address = insert(:address)
_transactions_1 =
2
|> insert_list(:transaction, from_address: address)
_transactions_2 =
2
|> insert_list(:transaction, from_address: address)
transactions_3 =
2
|> insert_list(:transaction, from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}",
# page number
"page" => "1",
# page size
"offset" => "2"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
page1_hashes = Enum.map(response["result"], & &1["hash"])
assert length(response["result"]) == 2
for transaction <- transactions_3 do
assert "#{transaction.hash}" in page1_hashes
end
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "ignores pagination params when invalid", %{conn: conn} do
address = insert(:address)
_transactions_1 =
2
|> insert_list(:transaction, from_address: address)
_transactions_2 =
2
|> insert_list(:transaction, from_address: address)
_transactions_3 =
2
|> insert_list(:transaction, from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}",
# page number
"page" => "invalidpage",
# page size
"offset" => "invalidoffset"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 6
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "ignores offset param if offset is less than 1", %{conn: conn} do
address = insert(:address)
6
|> insert_list(:transaction, from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}",
# page number
"page" => "1",
# page size
"offset" => "0"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 6
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "ignores offset param if offset is over 10,000", %{conn: conn} do
address = insert(:address)
6
|> insert_list(:transaction, from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}",
# page number
"page" => "1",
# page size
"offset" => "10_500"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 6
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "with page number with no results", %{conn: conn} do
address = insert(:address)
_transactions_1 =
2
|> insert_list(:transaction, from_address: address)
_transactions_2 =
2
|> insert_list(:transaction, from_address: address)
_transactions_3 =
2
|> insert_list(:transaction, from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}",
# page number
"page" => "5",
# page size
"offset" => "2"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == []
assert response["status"] == "0"
assert response["message"] == "No transactions found"
assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
end
test "supports GET and POST requests", %{conn: conn} do
address = insert(:address)
:transaction
|> insert(from_address: address)
params = %{
"module" => "account",
"action" => "pendingtxlist",
"address" => "#{address.hash}"
}
assert get_response =
conn
|> get("/api", params)
|> json_response(200)
assert post_response =
conn
|> post("/api", params)
|> json_response(200)
assert get_response == post_response
end
end
describe "txlistinternal" do describe "txlistinternal" do
test "with missing txhash and address", %{conn: conn} do test "with missing txhash and address", %{conn: conn} do
params = %{ params = %{

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

@ -22,7 +22,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end end
test "error for invalid address" do test "error for invalid address" do
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00") path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00", type: :regular, action: :read)
conn = conn =
build_conn() build_conn()
@ -49,7 +49,12 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
blockchain_get_function_mock() blockchain_get_function_mock()
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash) path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
type: :regular,
action: :read
)
conn = conn =
build_conn() build_conn()
@ -59,6 +64,112 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
assert conn.status == 200 assert conn.status == 200
refute conn.assigns.read_only_functions == [] refute conn.assigns.read_only_functions == []
end end
test "lists [] proxy read only functions if no verified implementation" do
token_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: token_contract_address.hash,
abi: [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
}
]
)
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
type: :proxy,
action: :read
)
conn =
build_conn()
|> put_req_header("x-requested-with", "xmlhttprequest")
|> get(path)
assert conn.status == 200
assert conn.assigns.read_only_functions == []
end
test "lists [] proxy read only functions if no verified eip-1967 implementation" do
token_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: token_contract_address.hash,
abi: [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
"name" => "implementation",
"inputs" => [],
"constant" => false
}
]
)
blockchain_get_implementation_mock()
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
type: :proxy,
action: :read
)
conn =
build_conn()
|> put_req_header("x-requested-with", "xmlhttprequest")
|> get(path)
assert conn.status == 200
assert conn.assigns.read_only_functions == []
end
test "lists [] proxy read only functions if no verified eip-1967 implementation and eth_getStorageAt returns not nnormalized address hash" do
token_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: token_contract_address.hash,
abi: [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
"name" => "implementation",
"inputs" => [],
"constant" => false
}
]
)
blockchain_get_implementation_mock_2()
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
type: :proxy,
action: :read
)
conn =
build_conn()
|> put_req_header("x-requested-with", "xmlhttprequest")
|> get(path)
assert conn.status == 200
assert conn.assigns.read_only_functions == []
end
end end
describe "GET show/3" do describe "GET show/3" do
@ -156,4 +267,24 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end end
) )
end end
defp blockchain_get_implementation_mock do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{id: _, method: _, params: [_, _, _]}, _options ->
{:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
end
)
end
defp blockchain_get_implementation_mock_2 do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{id: _, method: _, params: [_, _, _]}, _options ->
{:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
end
)
end
end end

@ -0,0 +1,19 @@
defmodule BlockScoutWeb.AddressReadProxyViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.AddressReadProxyView
describe "queryable?/1" do
test "returns true if list of inputs is not empty" do
assert AddressReadProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
assert AddressReadProxyView.queryable?([]) == false
end
end
describe "address?/1" do
test "returns true if type equals `address`" do
assert AddressReadProxyView.address?("address") == true
assert AddressReadProxyView.address?("uint256") == false
end
end
end

@ -0,0 +1,19 @@
defmodule BlockScoutWeb.AddressWriteContractViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.AddressWriteContractView
describe "queryable?/1" do
test "returns true if list of inputs is not empty" do
assert AddressWriteContractView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
assert AddressWriteContractView.queryable?([]) == false
end
end
describe "address?/1" do
test "returns true if type equals `address`" do
assert AddressWriteContractView.address?("address") == true
assert AddressWriteContractView.address?("uint256") == false
end
end
end

@ -0,0 +1,19 @@
defmodule BlockScoutWeb.AddressWriteProxyViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.AddressWriteProxyView
describe "queryable?/1" do
test "returns true if list of inputs is not empty" do
assert AddressWriteProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
assert AddressWriteProxyView.queryable?([]) == false
end
end
describe "address?/1" do
test "returns true if type equals `address`" do
assert AddressWriteProxyView.address?("address") == true
assert AddressWriteProxyView.address?("uint256") == false
end
end
end

@ -17,6 +17,183 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end end
end end
describe "writeable?" do
test "returns true when there is write function" do
function = %{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
"constant" => false
}
assert SmartContractView.writeable?(function)
end
test "returns false when it is not a write function" do
function = %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
}
refute SmartContractView.writeable?(function)
end
test "returns false when there is no function" do
function = %{}
refute SmartContractView.writeable?(function)
end
test "returns false when there function is nil" do
function = nil
refute SmartContractView.writeable?(function)
end
end
describe "outputs?" do
test "returns true when there are outputs" do
outputs = [%{"name" => "_narcoId", "type" => "uint256"}]
assert SmartContractView.outputs?(outputs)
end
test "returns false when there are no outputs" do
outputs = []
refute SmartContractView.outputs?(outputs)
end
end
describe "payable?" do
test "returns true when there is payable function" do
function = %{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
}
assert SmartContractView.payable?(function)
end
test "returns true when there is old-style payable function" do
function = %{
"type" => "function",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
}
assert SmartContractView.payable?(function)
end
test "returns false when it is nonpayable function" do
function = %{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
}
refute SmartContractView.payable?(function)
end
test "returns false when there is no function" do
function = %{}
refute SmartContractView.payable?(function)
end
test "returns false when function is nil" do
function = nil
refute SmartContractView.payable?(function)
end
end
describe "nonpayable?" do
test "returns true when there is nonpayable function" do
function = %{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
}
assert SmartContractView.nonpayable?(function)
end
test "returns true when there is old-style nonpayable function" do
function = %{
"type" => "function",
"outputs" => [],
"name" => "test",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
}
assert SmartContractView.nonpayable?(function)
end
test "returns false when it is payable function" do
function = %{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
}
refute SmartContractView.nonpayable?(function)
end
test "returns true when there is no function" do
function = %{}
refute SmartContractView.nonpayable?(function)
end
test "returns false when function is nil" do
function = nil
refute SmartContractView.nonpayable?(function)
end
end
describe "address?" do describe "address?" do
test "returns true when the type is equal to the string 'address'" do test "returns true when the type is equal to the string 'address'" do
type = "address" type = "address"

@ -0,0 +1,305 @@
# credo:disable-for-this-file
defmodule EthereumJSONRPC.Besu do
@moduledoc """
Ethereum JSONRPC methods that are only supported by [Besu](https://besu.hyperledger.org/en/stable/Reference/API-Methods).
"""
require Logger
import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces}
alias EthereumJSONRPC.{Transaction, Transactions}
@behaviour EthereumJSONRPC.Variant
@impl EthereumJSONRPC.Variant
def fetch_beneficiaries(block_numbers, json_rpc_named_arguments)
when is_list(block_numbers) and is_list(json_rpc_named_arguments) do
id_to_params =
block_numbers
|> block_numbers_to_params_list()
|> id_to_params()
with {:ok, responses} <-
id_to_params
|> FetchedBeneficiaries.requests()
|> json_rpc(json_rpc_named_arguments) do
{:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)}
end
end
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Besu trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
id_to_params = id_to_params(block_numbers)
with {:ok, responses} <-
id_to_params
|> trace_replay_block_transactions_requests()
|> json_rpc(json_rpc_named_arguments) do
trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
end
end
@impl EthereumJSONRPC.Variant
def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
id_to_params = id_to_params(transactions_params)
trace_replay_transaction_response =
id_to_params
|> trace_replay_transaction_requests()
|> json_rpc(json_rpc_named_arguments)
case trace_replay_transaction_response do
{:ok, responses} ->
case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do
{:ok, [first_trace]} ->
%{block_hash: block_hash} =
transactions_params
|> Enum.at(0)
{:ok,
[%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]}
{:error, error} ->
Logger.error(inspect(error))
{:error, error}
end
{:error, :econnrefused} ->
{:error, :econnrefused}
{:error, [error]} ->
Logger.error(inspect(error))
{:error, error}
end
end
@doc """
Fetches the pending transactions from the Besu node.
*NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based
on the transactions that each node has seen and how each node prioritizes collating transactions into the next block.
"""
@impl EthereumJSONRPC.Variant
@spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
{:ok, [Transaction.params()]} | {:error, reason :: term}
def fetch_pending_transactions(json_rpc_named_arguments) do
with {:ok, transactions} <-
%{id: 1, method: "txpool_besuTransactions", params: []}
|> request()
|> json_rpc(json_rpc_named_arguments) do
transactions_params =
transactions
|> Transactions.to_elixir()
|> Transactions.elixir_to_params()
{:ok, transactions_params}
end
end
defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do
Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
end
defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
params =
traces
|> Traces.to_elixir()
|> Traces.elixir_to_params()
{:ok, params}
end
end
defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params))
|> Enum.reduce(
{:ok, []},
fn
{:ok, traces}, {:ok, acc_traces_list} ->
{:ok, [traces | acc_traces_list]}
{:ok, _}, {:error, _} = acc_error ->
acc_error
{:error, reason}, {:ok, _} ->
{:error, [reason]}
{:error, reason}, {:error, acc_reason} ->
{:error, [reason | acc_reason]}
end
)
|> case do
{:ok, traces_list} ->
traces =
traces_list
|> Enum.reverse()
|> List.flatten()
{:ok, traces}
{:error, reverse_reasons} ->
reasons = Enum.reverse(reverse_reasons)
{:error, reasons}
end
end
defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
when is_list(results) and is_map(id_to_params) do
block_number = Map.fetch!(id_to_params, id)
annotated_traces =
results
|> Stream.with_index()
|> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
traces
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockNumber" => block_number,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index,
"index" => index
})
end)
end)
{:ok, annotated_traces}
end
defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
when is_map(id_to_params) do
block_number = Map.fetch!(id_to_params, id)
annotated_error =
Map.put(error, :data, %{
"blockNumber" => block_number
})
{:error, annotated_error}
end
defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, block_number} ->
trace_replay_block_transactions_request(%{id: id, block_number: block_number})
end)
end
defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
end
def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do
params =
traces
|> Traces.to_elixir()
|> Traces.elixir_to_params()
{:ok, params}
end
end
defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params))
|> Enum.reduce(
{:ok, []},
fn
{:ok, traces}, {:ok, acc_traces_list} ->
{:ok, [traces | acc_traces_list]}
{:ok, _}, {:error, _} = acc_error ->
acc_error
{:error, reason}, {:ok, _} ->
{:error, [reason]}
{:error, reason}, {:error, acc_reason} ->
{:error, [reason | acc_reason]}
end
)
|> case do
{:ok, traces_list} ->
traces =
traces_list
|> Enum.reverse()
|> List.flatten()
{:ok, traces}
{:error, reverse_reasons} ->
reasons = Enum.reverse(reverse_reasons)
{:error, reasons}
end
end
defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params)
when is_list(traces) and is_map(id_to_params) do
%{
block_hash: block_hash,
block_number: block_number,
hash_data: transaction_hash,
transaction_index: transaction_index
} = Map.fetch!(id_to_params, id)
first_trace =
traces
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockHash" => block_hash,
"blockNumber" => block_number,
"index" => index,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
end)
|> Enum.filter(fn trace ->
Map.get(trace, "index") == 0
end)
{:ok, first_trace}
end
defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params)
when is_map(id_to_params) do
%{
block_hash: block_hash,
block_number: block_number,
hash_data: transaction_hash,
transaction_index: transaction_index
} = Map.fetch!(id_to_params, id)
annotated_error =
Map.put(error, :data, %{
"blockHash" => block_hash,
"blockNumber" => block_number,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
{:error, annotated_error}
end
defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
trace_replay_transaction_request(%{id: id, hash_data: hash_data})
end)
end
defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
end
end

@ -0,0 +1,196 @@
defmodule EthereumJSONRPC.Besu.FetchedBeneficiaries do
@moduledoc """
Beneficiaries and errors from batch requests to `trace_block`.
"""
import EthereumJSONRPC, only: [quantity_to_integer: 1]
@doc """
Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`.
responses - List with trace_block responses
id_to_params - Maps request id to query params
## Examples
iex> EthereumJSONRPC.Besu.FetchedBeneficiaries.from_responses(
...> [
...> %{
...> id: 0,
...> result: [
...> %{
...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"},
...> "blockHash" => "0xFFF",
...> "blockNumber" => 12,
...> "result" => nil,
...> "subtraces" => 0,
...> "traceAddress" => [],
...> "transactionHash" => nil,
...> "transactionPosition" => nil,
...> "type" => "reward"
...> },
...> %{
...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"},
...> "blockHash" => "0xFFF",
...> "blockNumber" => 12,
...> "result" => nil,
...> "subtraces" => 0,
...> "traceAddress" => [],
...> "transactionHash" => nil,
...> "transactionPosition" => nil,
...> "type" => "reward"
...> }
...> ]
...> }
...> ],
...> %{0 => %{block_quantity: "0xC"}}
...> )
%EthereumJSONRPC.FetchedBeneficiaries{
errors: [],
params_set: #MapSet<[
%{
address_hash: "0x1",
address_type: :validator,
block_hash: "0xFFF",
block_number: 12,
reward: "0x0"
},
%{
address_hash: "0x2",
address_type: :emission_funds,
block_hash: "0xFFF",
block_number: 12,
reward: "0x0"
}
]>
}
"""
def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&response_to_params_set(&1, id_to_params))
|> Enum.reduce(
%EthereumJSONRPC.FetchedBeneficiaries{},
fn
{:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc ->
%EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)}
{:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc ->
%EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]}
end
)
end
@doc """
`trace_block` requests for `id_to_params`.
"""
def requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity}} ->
request(%{id: id, block_quantity: block_quantity})
end)
end
@spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) ::
{:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}}
when id: non_neg_integer(), block_quantity: String.t()
defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do
%{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
{:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}}
end
@spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) ::
{:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())}
when id: non_neg_integer(), block_quantity: String.t()
defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do
%{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
block_number = quantity_to_integer(block_quantity)
params_set = traces_to_params_set(traces, block_number)
{:ok, params_set}
end
@spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{
id => %{block_quantity: block_quantity}
}) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}}
when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t()
defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do
%{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
annotated_error = Map.put(error, :data, %{block_quantity: block_quantity})
{:error, annotated_error}
end
defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do
EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]})
end
defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do
traces
|> Stream.filter(&(&1["type"] == "reward"))
|> Stream.with_index()
|> Enum.reduce(MapSet.new(), fn {trace, index}, acc ->
MapSet.union(acc, trace_to_params_set(trace, block_number, index))
end)
end
defp trace_to_params_set(
%{
"action" => %{
"rewardType" => reward_type,
"author" => address_hash_data,
"value" => reward_value
},
"blockHash" => block_hash,
"blockNumber" => block_number
},
block_number,
index
)
when is_integer(block_number) and reward_type in ~w(block external uncle) do
MapSet.new([
%{
address_hash: address_hash_data,
block_hash: block_hash,
block_number: block_number,
reward: reward_value,
address_type: get_address_type(reward_type, index)
}
])
end
# Beneficiary's address type will depend on the responses' action.rewardType,
# which will vary depending on which network is being indexed
#
# On POA networks, rewardType will always be external and the type of the address being
# rewarded will depend on its position.
# First address will always be the validator's while the second will be the EmissionsFunds address
#
# On PoW networks, like Ethereum, the reward type will already specify the type for the
# address being rewarded
# The rewardType "block" will show the reward for the consensus block validator
# The rewardType "uncle" will show reward for validating an uncle block
defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds
defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 5, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 6, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 7, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 8, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 9, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 10, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 11, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 12, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 13, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 14, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 15, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 16, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 17, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 18, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 19, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 20, do: :validator
defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator
defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle
defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator
end

@ -41,7 +41,7 @@ defmodule EthereumJSONRPC.Contract do
|> Enum.map(fn {%{contract_address: contract_address, function_name: function_name, args: args} = request, index} -> |> Enum.map(fn {%{contract_address: contract_address, function_name: function_name, args: args} = request, index} ->
functions[function_name] functions[function_name]
|> Encoder.encode_function_call(args) |> Encoder.encode_function_call(args)
|> eth_call_request(contract_address, index, Map.get(request, :block_number)) |> eth_call_request(contract_address, index, Map.get(request, :block_number), Map.get(request, :from))
end) end)
|> json_rpc(json_rpc_named_arguments) |> json_rpc(json_rpc_named_arguments)
|> case do |> case do
@ -70,7 +70,7 @@ defmodule EthereumJSONRPC.Contract do
Enum.map(requests, fn _ -> format_error(error) end) Enum.map(requests, fn _ -> format_error(error) end)
end end
defp eth_call_request(data, contract_address, id, block_number) do defp eth_call_request(data, contract_address, id, block_number, from) do
block = block =
case block_number do case block_number do
nil -> "latest" nil -> "latest"
@ -80,10 +80,28 @@ defmodule EthereumJSONRPC.Contract do
request(%{ request(%{
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [%{to: contract_address, data: data}, block] params: [%{to: contract_address, data: data, from: from}, block]
}) })
end end
def eth_get_storage_at_request(contract_address, storage_pointer, block_number, json_rpc_named_arguments) do
block =
case block_number do
nil -> "latest"
block_number -> integer_to_quantity(block_number)
end
result =
%{id: 0, method: "eth_getStorageAt", params: [contract_address, storage_pointer, block]}
|> request()
|> json_rpc(json_rpc_named_arguments)
case result do
{:ok, storage_value} -> {:ok, storage_value}
other -> other
end
end
defp format_error(message) when is_binary(message) do defp format_error(message) when is_binary(message) do
{:error, message} {:error, message}
end end

@ -38,7 +38,7 @@ defmodule EthereumJSONRPC.Geth do
end end
@doc """ @doc """
Fetches the first trace from the Parity trace URL. Fetches the first trace from the trace URL.
""" """
@impl EthereumJSONRPC.Variant @impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore

@ -1,3 +1,4 @@
# credo:disable-for-this-file
defmodule EthereumJSONRPC.Parity do defmodule EthereumJSONRPC.Parity do
@moduledoc """ @moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).

@ -253,7 +253,7 @@ defmodule EthereumJSONRPC.Receipt do
# hash format # hash format
# gas is passed in from the `t:EthereumJSONRPC.Transaction.params/0` to allow pre-Byzantium status to be derived # gas is passed in from the `t:EthereumJSONRPC.Transaction.params/0` to allow pre-Byzantium status to be derived
defp entry_to_elixir({key, _} = entry) defp entry_to_elixir({key, _} = entry)
when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash), when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash revertReason),
do: {:ok, entry} do: {:ok, entry}
defp entry_to_elixir({key, quantity}) defp entry_to_elixir({key, quantity})

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

@ -207,6 +207,11 @@ config :explorer, Explorer.Chain.Cache.Accounts,
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.PendingTransactions, config :explorer, Explorer.Chain.Cache.PendingTransactions,
enabled:
if(System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu",
do: false,
else: true
),
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false), ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))

@ -0,0 +1,25 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
method_to_url: [
eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Besu
],
subscribe_named_arguments: [
transport: EthereumJSONRPC.WebSocket,
transport_options: [
web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
],
variant: EthereumJSONRPC.Besu
]

@ -0,0 +1,25 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
method_to_url: [
eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Besu
],
subscribe_named_arguments: [
transport: EthereumJSONRPC.WebSocket,
transport_options: [
web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
],
variant: EthereumJSONRPC.Besu
]

@ -0,0 +1,14 @@
use Mix.Config
config :explorer,
transport: EthereumJSONRPC.HTTP,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.Mox,
transport_options: [],
variant: EthereumJSONRPC.Besu
],
subscribe_named_arguments: [
transport: EthereumJSONRPC.Mox,
transport_options: [],
variant: EthereumJSONRPC.Besu
]

@ -28,6 +28,9 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Contract
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.LastFetchedCounter alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain alias Explorer.Chain
@ -72,6 +75,7 @@ defmodule Explorer.Chain do
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter} alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.Reader
alias Dataloader.Ecto, as: DataloaderEcto alias Dataloader.Ecto, as: DataloaderEcto
@ -2446,7 +2450,7 @@ defmodule Explorer.Chain do
|> Repo.all() |> Repo.all()
end end
defp pending_transactions_query(query) do def pending_transactions_query(query) do
from(transaction in query, from(transaction in query,
where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced") where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced")
) )
@ -2715,6 +2719,68 @@ defmodule Explorer.Chain do
def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error} def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error}
def transaction_to_revert_reason(transaction) do
%Transaction{revert_reason: revert_reason} = transaction
if revert_reason == nil do
fetch_tx_revert_reason(transaction)
else
revert_reason
end
end
def fetch_tx_revert_reason(
%Transaction{
block_number: block_number,
to_address_hash: to_address_hash,
from_address_hash: from_address_hash,
input: data,
gas: gas,
gas_price: gas_price,
value: value
} = transaction
) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
req =
EthereumJSONRPCTransaction.eth_call_request(
0,
block_number,
data,
to_address_hash,
from_address_hash,
Decimal.to_integer(gas),
Wei.hex_format(gas_price),
Wei.hex_format(value)
)
data =
case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do
{:error, %{data: data}} ->
data
_ ->
""
end
revert_reason_parts = String.split(data, "revert: ")
formatted_revert_reason =
if Enum.count(revert_reason_parts) > 1 do
Enum.at(revert_reason_parts, 1)
else
data
end
if byte_size(formatted_revert_reason) > 0 do
transaction
|> Changeset.change(%{revert_reason: formatted_revert_reason})
|> Repo.update()
end
formatted_revert_reason
end
@doc """ @doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`. `unit`.
@ -3499,56 +3565,63 @@ defmodule Explorer.Chain do
|> page_coin_balances(paging_options) |> page_coin_balances(paging_options)
|> Repo.all() |> Repo.all()
min_block_number = if Enum.empty?(balances_raw) do
balances_raw
|> Enum.min_by(fn balance -> balance.block_number end)
|> Map.get(:block_number)
max_block_number =
balances_raw balances_raw
|> Enum.max_by(fn balance -> balance.block_number end) else
|> Map.get(:block_number) balances_raw_filtered =
min_block_timestamp = find_block_timestamp(min_block_number)
max_block_timestamp = find_block_timestamp(max_block_number)
min_block_unix_timestamp =
min_block_timestamp
|> Timex.to_unix()
max_block_unix_timestamp =
max_block_timestamp
|> Timex.to_unix()
blocks_delta = max_block_number - min_block_number
balances_with_dates =
if blocks_delta > 0 do
balances_raw
|> Enum.map(fn balance ->
date =
trunc(
min_block_unix_timestamp +
(balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) /
blocks_delta
)
formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date}
end)
else
balances_raw balances_raw
|> Enum.map(fn balance -> |> Enum.filter(fn balance -> balance.value end)
date = min_block_unix_timestamp
min_block_number =
balances_raw_filtered
|> Enum.min_by(fn balance -> balance.block_number end, fn -> %{} end)
|> Map.get(:block_number)
max_block_number =
balances_raw_filtered
|> Enum.max_by(fn balance -> balance.block_number end, fn -> %{} end)
|> Map.get(:block_number)
min_block_timestamp = find_block_timestamp(min_block_number)
max_block_timestamp = find_block_timestamp(max_block_number)
min_block_unix_timestamp =
min_block_timestamp
|> Timex.to_unix()
max_block_unix_timestamp =
max_block_timestamp
|> Timex.to_unix()
blocks_delta = max_block_number - min_block_number
balances_with_dates =
if blocks_delta > 0 do
balances_raw_filtered
|> Enum.map(fn balance ->
date =
trunc(
min_block_unix_timestamp +
(balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) /
blocks_delta
)
formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date}
end)
else
balances_raw_filtered
|> Enum.map(fn balance ->
date = min_block_unix_timestamp
formatted_date = Timex.from_unix(date) formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date} %{balance | block_timestamp: formatted_date}
end) end)
end end
balances_with_dates balances_with_dates
|> Enum.filter(fn balance -> balance.value end) |> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end)
|> Enum.sort(fn balance1, balance2 -> balance1.block_timestamp >= balance2.block_timestamp end) end
end end
def get_coin_balance(address_hash, block_number) do def get_coin_balance(address_hash, block_number) do
@ -4266,6 +4339,129 @@ defmodule Explorer.Chain do
end end
end end
def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do
implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi)
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
[]
end
def is_proxy_contract?(abi) when not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
if implementation_method_abi, do: true, else: false
end
def is_proxy_contract?(abi) when is_nil(abi) do
false
end
def get_implementation_address_hash(proxy_address_hash, abi)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
implementation_method_abi_state_mutability = Map.get(implementation_method_abi, "stateMutability")
is_eip1967 = if implementation_method_abi_state_mutability == "nonpayable", do: true, else: false
if is_eip1967 do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
# https://eips.ethereum.org/EIPS/eip-1967
eip_1967_implementation_storage_pointer = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
{:ok, implementation_address} =
Contract.eth_get_storage_at_request(
proxy_address_hash,
eip_1967_implementation_storage_pointer,
nil,
json_rpc_named_arguments
)
if String.length(implementation_address) > 42 do
"0x" <> String.slice(implementation_address, -40, 40)
else
implementation_address
end
else
implementation_address =
case Reader.query_contract(proxy_address_hash, abi, %{
"implementation" => []
}) do
%{"implementation" => {:ok, [result]}} -> result
_ -> nil
end
if implementation_address do
"0x" <> Base.encode16(implementation_address, case: :lower)
else
nil
end
end
end
def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
nil
end
def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do
case Chain.string_to_address_hash(implementation_address_hash_string) do
{:ok, implementation_address_hash} ->
implementation_smart_contract =
implementation_address_hash
|> Chain.address_hash_to_smart_contract()
if implementation_smart_contract do
implementation_smart_contract
|> Map.get(:abi)
else
[]
end
_ ->
[]
end
end
def get_implementation_abi(implementation_address_hash_string) when is_nil(implementation_address_hash_string) do
[]
end
def get_implementation_abi_from_proxy(proxy_address_hash, abi)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
if implementation_method_abi do
implementation_address_hash_string = get_implementation_address_hash(proxy_address_hash, abi)
if implementation_address_hash_string do
get_implementation_abi(implementation_address_hash_string)
else
[]
end
else
[]
end
end
def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
[]
end
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} = {:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do if Map.has_key?(first_trace, :to_address_hash) do

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

@ -6,8 +6,8 @@ defmodule Explorer.Chain.Log do
require Logger require Logger
alias ABI.{Event, FunctionSelector} alias ABI.{Event, FunctionSelector}
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction} alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Repo
@required_attrs ~w(address_hash data block_hash index transaction_hash)a @required_attrs ~w(address_hash data block_hash index transaction_hash)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
@ -119,22 +119,35 @@ defmodule Explorer.Chain.Log do
@doc """ @doc """
Decode transaction log data. Decode transaction log data.
""" """
def decode(_log, %Transaction{to_address: nil}), do: {:error, :no_to_address}
def decode(log, transaction = %Transaction{to_address: %{smart_contract: %{abi: abi}}}) when not is_nil(abi) do def decode(log, transaction) do
with {:ok, selector, mapping} <- find_and_decode(abi, log, transaction), address_options = [
identifier <- Base.encode16(selector.method_id, case: :lower), necessity_by_association: %{
text <- function_call(selector.function, mapping), :smart_contract => :optional
do: {:ok, identifier, text, mapping} }
]
case Chain.find_contract_address(log.address_hash, address_options, true) do
{:ok, %{smart_contract: %{abi: abi}}} ->
full_abi = Chain.combine_proxy_implementation_abi(log.address_hash, abi)
with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping),
do: {:ok, identifier, text, mapping}
_ ->
find_candidates(log, transaction)
end
end end
def decode(log, transaction) do defp find_candidates(log, transaction) do
case log.first_topic do case log.first_topic do
"0x" <> hex_part -> "0x" <> hex_part ->
case Integer.parse(hex_part, 16) do case Integer.parse(hex_part, 16) do
{number, ""} -> {number, ""} ->
<<method_id::binary-size(4), _rest::binary>> = :binary.encode_unsigned(number) <<method_id::binary-size(4), _rest::binary>> = :binary.encode_unsigned(number)
find_candidates(method_id, log, transaction) find_candidates_query(method_id, log, transaction)
_ -> _ ->
{:error, :could_not_decode} {:error, :could_not_decode}
@ -145,7 +158,7 @@ defmodule Explorer.Chain.Log do
end end
end end
defp find_candidates(method_id, log, transaction) do defp find_candidates_query(method_id, log, transaction) do
candidates_query = candidates_query =
from( from(
contract_method in ContractMethod, contract_method in ContractMethod,

@ -255,20 +255,26 @@ defmodule Explorer.Chain.SmartContract do
|> prepare_changes(&upsert_contract_methods/1) |> prepare_changes(&upsert_contract_methods/1)
end end
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error) do def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error, error_message) do
smart_contract validated =
|> cast(attrs, [ smart_contract
:name, |> cast(attrs, [
:compiler_version, :name,
:optimization, :compiler_version,
:contract_source_code, :optimization,
:address_hash, :contract_source_code,
:evm_version, :address_hash,
:optimization_runs, :evm_version,
:constructor_arguments :optimization_runs,
]) :constructor_arguments
|> validate_required([:name, :compiler_version, :optimization, :address_hash]) ])
|> add_error(:contract_source_code, error_message(error)) |> validate_required([:name, :compiler_version, :optimization, :address_hash])
if error_message do
add_error(validated, :contract_source_code, error_message(error, error_message))
else
add_error(validated, :contract_source_code, error_message(error))
end
end end
def add_submitted_comment(code, inserted_at) when is_binary(code) do def add_submitted_comment(code, inserted_at) when is_binary(code) do
@ -331,4 +337,5 @@ defmodule Explorer.Chain.SmartContract do
defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again." defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again."
defp error_message(:name), do: "Wrong contract name, please try again." defp error_message(:name), do: "Wrong contract name, please try again."
defp error_message(_), do: "There was an error validating your contract, please try again." defp error_message(_), do: "There was an error validating your contract, please try again."
defp error_message(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}"
end end

@ -85,12 +85,6 @@ defmodule Explorer.Chain.Supply.TokenBridge do
|> Enum.map(fn {key, _value} -> key end) |> Enum.map(fn {key, _value} -> key end)
|> List.first() |> List.first()
value =
case Reader.query_contract(address, abi, params) do
%{^method_name => {:ok, [result]}} -> result
_ -> 0
end
type = type =
abi abi
|> Enum.at(0) |> Enum.at(0)
@ -98,6 +92,19 @@ defmodule Explorer.Chain.Supply.TokenBridge do
|> Enum.at(0) |> Enum.at(0)
|> Map.get("type", "") |> Map.get("type", "")
value =
case Reader.query_contract(address, abi, params) do
%{^method_name => {:ok, [result]}} ->
result
_ ->
case type do
"address" -> "0x0000000000000000000000000000000000000000"
"uint256" -> 0
_ -> 0
end
end
case type do case type do
"address" -> "address" ->
"0x" <> Base.encode16(value) "0x" <> Base.encode16(value)

@ -11,6 +11,8 @@ defmodule Explorer.Chain.Transaction do
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Block, Block,
@ -26,11 +28,10 @@ defmodule Explorer.Chain.Transaction do
} }
alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.Transaction.{Fork, Status}
alias Explorer.Repo
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start @optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index created_contract_code_indexed_at status error gas_used index created_contract_code_indexed_at status
to_address_hash)a to_address_hash revert_reason)a
@required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a @required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
@ -106,6 +107,7 @@ defmodule Explorer.Chain.Transaction do
* `internal_transactions` - transactions (value transfers) created while executing contract used for this * `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction transaction
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer` * `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer`
* `revert_reason` - revert reason of transaction
| `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description | | `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description |
|----------|----------------------------------|------------|-----------------|-------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------| |----------|----------------------------------|------------|-----------------|-------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------|
@ -129,6 +131,7 @@ defmodule Explorer.Chain.Transaction do
* `uncles` - uncle blocks where `forks` were collated * `uncles` - uncle blocks where `forks` were collated
* `v` - The V field of the signature. * `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address` * `value` - wei transferred from `from_address` to `to_address`
* `revert_reason` - revert reason of transaction
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil, block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
@ -159,7 +162,8 @@ defmodule Explorer.Chain.Transaction do
to_address_hash: Hash.Address.t() | nil, to_address_hash: Hash.Address.t() | nil,
uncles: %Ecto.Association.NotLoaded{} | [Block.t()], uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
v: v(), v: v(),
value: Wei.t() value: Wei.t(),
revert_reason: String.t()
} }
@derive {Poison.Encoder, @derive {Poison.Encoder,
@ -199,6 +203,7 @@ defmodule Explorer.Chain.Transaction do
field(:status, Status) field(:status, Status)
field(:v, :decimal) field(:v, :decimal)
field(:value, Wei) field(:value, Wei)
field(:revert_reason, :string)
# A transient field for deriving old block hash during transaction upserts. # A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated # Used to force refetch of a block in case a transaction is re-collated
@ -419,7 +424,7 @@ defmodule Explorer.Chain.Transaction do
candidates_query candidates_query
|> Repo.all() |> Repo.all()
|> Enum.flat_map(fn candidate -> |> Enum.flat_map(fn candidate ->
case do_decoded_input_data(data, [candidate.abi], hash) do case do_decoded_input_data(data, [candidate.abi], nil, hash) do
{:ok, _, _, _} = decoded -> [decoded] {:ok, _, _, _} = decoded -> [decoded]
_ -> [] _ -> []
end end
@ -432,12 +437,18 @@ defmodule Explorer.Chain.Transaction do
{:error, :contract_not_verified, []} {:error, :contract_not_verified, []}
end end
def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do def decoded_input_data(%__MODULE__{
do_decoded_input_data(data, abi, hash) input: %{bytes: data},
to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}},
hash: hash
}) do
do_decoded_input_data(data, abi, address_hash, hash)
end end
defp do_decoded_input_data(data, abi, hash) do defp do_decoded_input_data(data, abi, address_hash, hash) do
with {:ok, {selector, values}} <- find_and_decode(abi, data, hash), full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
{:ok, mapping} <- selector_mapping(selector, values, hash), {:ok, mapping} <- selector_mapping(selector, values, hash),
identifier <- Base.encode16(selector.method_id, case: :lower), identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping), text <- function_call(selector.function, mapping),

@ -7,6 +7,7 @@ defmodule Explorer.ChainSpec.GenesisData do
require Logger require Logger
alias Explorer.ChainSpec.Geth.Importer, as: GethImporter
alias Explorer.ChainSpec.Parity.Importer alias Explorer.ChainSpec.Parity.Importer
alias HTTPoison.Response alias HTTPoison.Response
@ -58,11 +59,24 @@ defmodule Explorer.ChainSpec.GenesisData do
path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path] path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path]
if path do if path do
json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
Task.Supervisor.async_nolink(Explorer.GenesisDataTaskSupervisor, fn -> Task.Supervisor.async_nolink(Explorer.GenesisDataTaskSupervisor, fn ->
case fetch_spec(path) do case fetch_spec(path) do
{:ok, chain_spec} -> {:ok, chain_spec} ->
Importer.import_emission_rewards(chain_spec) case variant do
{:ok, _} = Importer.import_genesis_accounts(chain_spec) EthereumJSONRPC.Parity ->
Importer.import_emission_rewards(chain_spec)
{:ok, _} = Importer.import_genesis_accounts(chain_spec)
EthereumJSONRPC.Geth ->
{:ok, _} = GethImporter.import_genesis_accounts(chain_spec)
_ ->
Importer.import_emission_rewards(chain_spec)
{:ok, _} = Importer.import_genesis_accounts(chain_spec)
end
{:error, reason} -> {:error, reason} ->
Logger.warn(fn -> "Failed to fetch genesis data. #{inspect(reason)}" end) Logger.warn(fn -> "Failed to fetch genesis data. #{inspect(reason)}" end)

@ -0,0 +1,93 @@
# credo:disable-for-this-file
defmodule Explorer.ChainSpec.Geth.Importer do
@moduledoc """
Imports data from Geth genesis.json.
"""
require Logger
alias EthereumJSONRPC.Blocks
alias Explorer.Chain
alias Explorer.Chain.Hash.Address, as: AddressHash
def import_genesis_accounts(chain_spec) do
balance_params =
chain_spec
|> genesis_accounts()
|> Stream.map(fn balance_map ->
Map.put(balance_map, :block_number, 0)
end)
|> Enum.to_list()
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
EthereumJSONRPC.fetch_blocks_by_range(1..1, json_rpc_named_arguments)
day = DateTime.to_date(timestamp)
balance_daily_params =
chain_spec
|> genesis_accounts()
|> Stream.map(fn balance_map ->
Map.put(balance_map, :day, day)
end)
|> Enum.to_list()
address_params =
balance_params
|> Stream.map(fn %{address_hash: hash} = map ->
Map.put(map, :hash, hash)
end)
|> Enum.to_list()
params = %{
address_coin_balances: %{params: balance_params},
address_coin_balances_daily: %{params: balance_daily_params},
addresses: %{params: address_params}
}
Chain.import(params)
end
def genesis_accounts(chain_spec) do
accounts = chain_spec["alloc"]
if accounts do
parse_accounts(accounts)
else
Logger.warn(fn -> "No accounts are defined in genesis" end)
[]
end
end
defp parse_accounts(accounts) do
accounts
|> Stream.filter(fn {_address, map} ->
!is_nil(map["balance"])
end)
|> Stream.map(fn {address, %{"balance" => value} = params} ->
formatted_address = if String.starts_with?(address, "0x"), do: address, else: "0x" <> address
{:ok, address_hash} = AddressHash.cast(formatted_address)
balance = parse_number(value)
code = params["code"]
%{address_hash: address_hash, value: balance, contract_code: code}
end)
|> Enum.to_list()
end
defp parse_number("0x" <> hex_number) do
{number, ""} = Integer.parse(hex_number, 16)
number
end
defp parse_number(string_number) do
{number, ""} = Integer.parse(string_number, 10)
number
end
end

@ -1,6 +1,7 @@
# credo:disable-for-this-file
defmodule Explorer.ChainSpec.Parity.Importer do defmodule Explorer.ChainSpec.Parity.Importer do
@moduledoc """ @moduledoc """
Imports data from parity chain spec. Imports data from Parity/Open Ethereum chain spec.
""" """
require Logger require Logger

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

Loading…
Cancel
Save