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. 230
      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. 2219
      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. 23
      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. 37
      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. 26
      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. 214
      apps/explorer/lib/explorer/chain.ex
  93. 36
      apps/explorer/lib/explorer/chain/address/coin_balance.ex
  94. 27
      apps/explorer/lib/explorer/chain/log.ex
  95. 11
      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. 14
      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:
docker:
# Ensure .tool-versions matches
- image: circleci/node:12.16.1-browsers-legacy
- image: circleci/node:12.18.2-browsers-legacy
working_directory: ~/app
@ -275,7 +275,7 @@ jobs:
jest:
docker:
# Ensure .tool-versions matches
- image: circleci/node:12.16.1-browsers-legacy
- image: circleci/node:12.18.2-browsers-legacy
working_directory: ~/app

@ -1,16 +1,22 @@
:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
: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
lib/explorer/repo/prometheus_logger.ex:8
lib/block_scout_web/views/layout_view.ex:175
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: 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()
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'
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/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
erlang 22.2
nodejs 12.14.1
nodejs 12.18.2

@ -1,10 +1,71 @@
## 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
- [#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
- [#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)
- [#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

@ -29,3 +29,11 @@
height: 0;
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;
}
}
.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 {
background-color: $dropdown-menu-item-hover-background;
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};
}
.modal-header-group {
display: inline-flex;
margin: 0 auto;
}
.close.close-modal{
left: auto;
opacity: 1;
@ -18,6 +23,10 @@
right: -35px;
top: -35px;
outline: none !important;
path {
fill: #F6F7F9;
}
}
.close {
@ -35,6 +44,10 @@
text-align: left;
white-space: normal;
word-break: break-word;
.centered {
margin: 0 auto;
}
}
.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 {
align-items: center;
border-top-left-radius: $modal-border-radius;
border-top-right-radius: $modal-border-radius;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
display: flex;
height: 135px;
justify-content: center;
@ -66,6 +66,18 @@ $modal-status-graph-question: #329ae9 !default;
line-height: 1.5;
margin: 0 0 25px;
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 {
@ -76,6 +88,13 @@ $modal-status-graph-question: #329ae9 !default;
.btn-line {
flex-grow: 1;
margin-right: 20px;
border-color: $primary;
color: $primary;
&:hover {
background-color: $primary;
color: $additional-font;
}
&:last-child {
margin-right: 0;

@ -260,3 +260,18 @@ $navbar-logo-width: auto !default;
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");
@media (min-width: 1200px) {
.container {
max-width: 1260px !important;
}
}
// Grid columns
//
// Set the number of columns and specify the width of the gutters.

@ -1,75 +1,191 @@
import $ from 'jquery'
$(function () {
$('.js-become-candidate').on('click', function () {
$('#becomeCandidateModal').modal()
})
let $currentModal = null
let modalLocked = false
$('.js-validator-info-modal').on('click', function () {
$('#validatorInfoModal').modal()
})
const spinner =
`
<span class="loading-spinner-small mr-2">
<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) {
e.preventDefault()
e.stopPropagation()
return false
}
$('.js-remove-pool').on('click', function () {
$('#warningStatusModal').modal()
$currentModal = null
})
$('.js-copy-address').on('click', function () {
$('#successStatusModal').modal()
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)
}
$('.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())
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)
}
$(modal).modal()
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)
}
setupStakesProgress(progress, total, 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')
}
$('.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())
if (exceptCallback) {
$except.on('click', event => {
$closeButton.attr('disabled', true)
$(modal).modal()
$except
.unbind('click')
.attr('disabled', true)
.find('.btn-line-text').html(spinner)
$accept
.unbind('click')
.attr('disabled', true)
.removeAttr('data-dismiss')
setupStakesProgress(progress, total, modal)
modalLocked = true
exceptCallback($modal, event)
})
} else {
$except.attr('data-dismiss', '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
// }
// }
// })
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 '../../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",
"chart.js": "^2.9.3",
"clipboard": "^2.0.4",
"eth-net-props": "^1.0.33",
"highlight.js": "^9.16.2",
"highlightjs-solidity": "^1.0.8",
"humps": "^2.0.1",
"jquery": "^3.4.0",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"moment": "^2.24.0",
"nanomorph": "^5.4.0",
"numeral": "^2.0.6",
@ -39,7 +40,8 @@
"popper.js": "^1.14.7",
"reduce-reducers": "^0.4.3",
"redux": "^4.0.5",
"urijs": "^1.19.2"
"urijs": "^1.19.2",
"web3": "^1.2.9"
},
"devDependencies": {
"@babel/core": "^7.7.2",

@ -90,6 +90,7 @@ const appJs =
'admin-tasks': './js/pages/admin/tasks.js',
'read-token-contract': './js/pages/read_token_contract.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',
'try-api': './js/lib/try_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"),
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

@ -23,16 +23,6 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
{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 =
case next_page_params(next_page, coin_balances, params) do
nil ->
@ -48,7 +38,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
end
coin_balances_json =
Enum.map(deduplicated_coin_balances, fn coin_balance ->
Enum.map(coin_balances, fn coin_balance ->
View.render_to_string(
AddressCoinBalanceView,
"_coin_balances.html",

@ -31,6 +31,8 @@ defmodule BlockScoutWeb.AddressReadContractController do
conn,
"index.html",
address: address,
type: :regular,
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)})

@ -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)
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
options = optional_params(params)
@ -457,6 +480,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
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
case Etherscan.list_internal_transactions(transaction_hash) do
[] -> {:error, :not_found}

@ -17,6 +17,20 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
render(conn, :verify, %{contract: address})
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, _} ->
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.Transaction
def gettxinfo(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
{:transaction, {:ok, %Transaction{revert_reason: revert_reason, error: error} = transaction}} <-
transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs)
transaction_updated =
if error == "Reverted" do
if revert_reason == nil do
%Transaction{transaction | revert_reason: Chain.fetch_tx_revert_reason(transaction)}
else
transaction
end
else
transaction
end
render(conn, :gettxinfo, %{
transaction: transaction,
transaction: transaction_updated,
block_height: Chain.block_height(),
logs: logs,
next_page_params: next_page_params(next_page, logs, params)

@ -2,21 +2,64 @@ defmodule BlockScoutWeb.SmartContractController do
use BlockScoutWeb, :controller
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),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do
read_only_functions = Reader.read_only_functions(address_hash)
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
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
|> put_status(200)
|> put_layout(false)
|> render(
"_functions.html",
read_only_functions: read_only_functions,
address: address
read_only_functions: functions,
address: address,
contract_abi: contract_abi,
implementation_address: implementation_address_hash_string,
implementation_abi: implementation_abi,
contract_type: contract_type,
action: action
)
else
:error ->
@ -33,13 +76,26 @@ defmodule BlockScoutWeb.SmartContractController do
def index(conn, _), do: not_found(conn)
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),
{: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 =
Reader.query_function(
address_hash,
%{name: params["function_name"], args: params["args"]}
%{name: params["function_name"], args: params["args"]},
contract_type
)
conn

@ -13,6 +13,8 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
render(
conn,
"index.html",
type: :regular,
action: :read,
token: Market.add_price(token),
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 %{
"status" => "1",
"message" => "OK",
@ -470,7 +491,8 @@ defmodule BlockScoutWeb.Etherscan do
"success" => true,
"timeStamp" => "1541018182",
"to" => "0x000000000000000000000000000000000000000d",
"value" => "67612"
"value" => "67612",
revertReason: "No credit of that type"
}
}
@ -609,6 +631,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("18")
}
@revert_reason_type %{
type: "revert_reason",
definition: "Revert reason of transaction.",
example: ~s("No credit of that type")
}
@logs_details %{
name: "Log Detail",
fields: %{
@ -1010,7 +1038,8 @@ defmodule BlockScoutWeb.Etherscan do
logs: %{
type: "array",
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 %{
name: "txlist",
description:
@ -2021,9 +2100,9 @@ defmodule BlockScoutWeb.Etherscan do
<div class='tab-pane fade show active'>
<div class="tile tile-muted p-1">
<div class="m-2">
curl -d '{"addressHash":"0xd6984e092b51337032cf0300c7291e4839be37e1","compilerVersion":"v0.5.4+commit.9549d8ff",
"contractSourceCode":"pragma solidity ^0.5.4;\n","name":"Test","optimization":false}'
-H "Content-Type: application/json" -X POST "https://blockscout.com/eth/kovan/api?module=contract&action=verify"
curl -d '{"addressHash":"0xc63BB6555C90846afACaC08A0F0Aa5caFCB382a1","compilerVersion":"v0.5.4+commit.9549d8ff",
"contractSourceCode":"pragma solidity ^0.5.4; \ncontract Test {\n}","name":"Test","optimization":false}'
-H "Content-Type: application/json" -X POST "https://blockscout.com/poa/sokol/api?module=contract&action=verify"
</pre>
</div>
</div>
@ -2343,6 +2422,7 @@ defmodule BlockScoutWeb.Etherscan do
@account_eth_get_balance_action,
@account_balance_action,
@account_balancemulti_action,
@account_pendingtxlist_action,
@account_txlist_action,
@account_txlistinternal_action,
@account_tokentx_action,

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

@ -64,4 +64,25 @@
class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%>
<% 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>

@ -46,45 +46,7 @@
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<div class="table-responsive text-center">
<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>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% {:error, :contract_not_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
@ -99,44 +61,7 @@
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<div class="table-responsive text-center">
<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>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
</div>
<% end %>
<% _ -> %>

@ -5,7 +5,7 @@
<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-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>
<span class="loading-spinner-small mr-2">
<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-status-graph modal-status-graph-<%= if assigns[:status] do @status end %>">
<%=
if @status == "error" do
render BlockScoutWeb.CommonComponentsView, "_icon_error_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"
if @status in ["error", "success", "warning", "question"] do
render BlockScoutWeb.CommonComponentsView, "_icon_#{@status}_modal.html"
end
%>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %>
<div class="modal-body modal-status-body">
<%= if assigns[:title] do %>
<h2 class="modal-status-title"><%= @title %></h2>
<% end %>
<%= if assigns[:text] do %>
<p class="modal-status-text"><%= @text %></p>
<% end %>
<h2 class="modal-status-title"><%= if assigns[:title] do @title end %></h2>
<p class="modal-status-text"><%= if assigns[:text] do @text end %></p>
<div class="modal-status-button-wrapper">
<%= if @status !== "question" do %>
<button class="btn-line" type="button" data-dismiss="modal">
<span class="btn-line-text">Ok</span>
</button>
<% 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>
</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>
</button>
<% 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)}",
to: transaction_path(@conn, :index)
) %>
<% json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)%>
<% variant = Keyword.fetch!(json_rpc_named_arguments, :variant) %>
<%= if variant !== EthereumJSONRPC.Besu do %>
<%= link(
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>
</li>
<li class="nav-item">
@ -99,6 +103,25 @@
</div>
</li>
<% 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">
<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">

@ -86,6 +86,9 @@
@view_module != Elixir.BlockScoutWeb.AddressContractView &&
@view_module != Elixir.BlockScoutWeb.AddressContractVerificationView &&
@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.ReadContractView &&
@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 %>
<div class="d-flex py-2 border-bottom" data-function>
<div class="py-2 pr-2 text-nowrap">
@ -8,20 +19,35 @@
&#8594;
</div>
<%= if queryable?(function["inputs"]) do %>
<%= if queryable?(function["inputs"]) || writeable?(function) do %>
<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"] %>' />
<%= 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">
<input type="text" name="function_input" tx-value class="form-control form-control-sm address-input-sm mt-2" placeholder='value(<%= gettext("ETH")%>)' />
</div>
<% 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>
<%= if outputs?(function["outputs"]) do %>
<div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'>
<%= if (queryable?(function["inputs"])), do: raw "&#8627;" %>
@ -29,11 +55,13 @@
<%= output["type"] %>
<% end %>
</div>
<% end %>
<div data-function-response></div>
</div>
<% else %>
<span class="py-2">
<span class="py-2 word-break-all">
<%= if outputs?(function["outputs"]) do %>
<%= for output <- function["outputs"] do %>
<%= if address?(output["type"]) do %>
<%= link(
@ -55,6 +83,7 @@
<% end %>
<% end %>
<% end %>
<% end %>
</span>
<% end %>
</div>

@ -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">
<%= render OverviewView, "_tabs.html", assigns %>
<!-- 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">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>

@ -15,14 +15,21 @@
<div class="table-responsive text-center">
<table style="color: black; table-layout: fixed;" summary="<%= gettext "Transaction Inputs" %>" class="table thead-light table-bordered">
<tr>
<th scope="col" style="width: 60px;"></th>
<th scope="col" style="width: 10%;"><%= gettext "Name" %></th>
<th scope="col" style="width: 10%;"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, value} <- @mapping do %>
<tr>
<th scope="row">
<td><%= name %></td>
<td><%= type %></td>
<td align=left>
<%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
<% :error -> %>
<div class="alert alert-danger">
<%= gettext "Error rendering value" %>
</div>
<% _value -> %>
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
@ -33,23 +40,14 @@
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="32" height="32">
<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 %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td>
<%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
<% :error -> %>
<div class="alert alert-danger">
<%= gettext "Error rendering value" %>
</div>
<% value -> %>
<pre class="transaction-input-text tile pre-wrap"><code><%= value %></code></pre>
<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 %>
</td>
</tr>

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

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

@ -49,45 +49,7 @@
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<div class="table-responsive text-center">
<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>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% {:error, :contract_not_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
@ -102,45 +64,7 @@
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<div class="table-responsive text-center">
<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>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% end %>
<% _ -> %>
<%= nil %>

@ -4,7 +4,22 @@
<div class="card">
<%= render BlockScoutWeb.TransactionView, "_tabs.html", assigns %>
<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 %>
<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 %>

@ -1,7 +1,13 @@
defmodule BlockScoutWeb.AddressReadContractView do
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"
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.Block.Reward
alias Explorer.ExchangeRates.Token, as: TokenExchangeRate
alias Explorer.SmartContract.Writer
@dialyzer :no_match
@ -18,6 +19,9 @@ defmodule BlockScoutWeb.AddressView do
"internal_transactions",
"token_transfers",
"read_contract",
"read_proxy",
"write_contract",
"write_proxy",
"tokens",
"transactions",
"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_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
address.has_decompiled_code? ||
(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(["decompiled_contracts"]), do: gettext("Decompiled Code")
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(["validations"]), do: gettext("Blocks Validated")
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)
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
data = Enum.map(transactions, &prepare_transaction/1)
RPCView.render("show.json", data: data)
@ -79,6 +84,22 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
}
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
%{
"blockNumber" => "#{transaction.block_number}",

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

@ -17,12 +17,7 @@ defmodule BlockScoutWeb.LayoutView do
},
%{
title: "xDai Chain",
url: "https://blockscout.com/poa/dai"
},
%{
title: "Kovan Testnet",
url: "https://blockscout.com/eth/kovan",
test_net?: true
url: "https://blockscout.com/poa/xdai"
},
%{
title: "Ethereum Classic",
@ -221,6 +216,21 @@ defmodule BlockScoutWeb.LayoutView do
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
case URI.parse(url) do
%URI{host: nil} -> :error

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

@ -1,7 +1,35 @@
defmodule BlockScoutWeb.SmartContractView do
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"]

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

@ -125,6 +125,27 @@ defmodule BlockScoutWeb.WebRouter do
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(
"/token_transfers",
AddressTokenTransferController,

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

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

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

@ -22,7 +22,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
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 =
build_conn()
@ -49,7 +49,12 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
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 =
build_conn()
@ -59,6 +64,112 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
assert conn.status == 200
refute conn.assigns.read_only_functions == []
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
describe "GET show/3" do
@ -156,4 +267,24 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
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

@ -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
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
test "returns true when the type is equal to the string 'address'" do
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} ->
functions[function_name]
|> 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)
|> json_rpc(json_rpc_named_arguments)
|> case do
@ -70,7 +70,7 @@ defmodule EthereumJSONRPC.Contract do
Enum.map(requests, fn _ -> format_error(error) 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 =
case block_number do
nil -> "latest"
@ -80,10 +80,28 @@ defmodule EthereumJSONRPC.Contract do
request(%{
id: id,
method: "eth_call",
params: [%{to: contract_address, data: data}, block]
params: [%{to: contract_address, data: data, from: from}, block]
})
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
{:error, message}
end

@ -38,7 +38,7 @@ defmodule EthereumJSONRPC.Geth do
end
@doc """
Fetches the first trace from the Parity trace URL.
Fetches the first trace from the trace URL.
"""
@impl EthereumJSONRPC.Variant
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
@moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).

@ -253,7 +253,7 @@ defmodule EthereumJSONRPC.Receipt do
# hash format
# 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)
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}
defp entry_to_elixir({key, quantity})

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

@ -207,6 +207,11 @@ config :explorer, Explorer.Chain.Cache.Accounts,
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
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),
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.{Changeset, Multi}
alias EthereumJSONRPC.Contract
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain
@ -72,6 +75,7 @@ defmodule Explorer.Chain do
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.Reader
alias Dataloader.Ecto, as: DataloaderEcto
@ -2446,7 +2450,7 @@ defmodule Explorer.Chain do
|> Repo.all()
end
defp pending_transactions_query(query) do
def pending_transactions_query(query) do
from(transaction in query,
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_revert_reason(transaction) do
%Transaction{revert_reason: revert_reason} = transaction
if revert_reason == nil do
fetch_tx_revert_reason(transaction)
else
revert_reason
end
end
def fetch_tx_revert_reason(
%Transaction{
block_number: block_number,
to_address_hash: to_address_hash,
from_address_hash: from_address_hash,
input: data,
gas: gas,
gas_price: gas_price,
value: value
} = transaction
) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
req =
EthereumJSONRPCTransaction.eth_call_request(
0,
block_number,
data,
to_address_hash,
from_address_hash,
Decimal.to_integer(gas),
Wei.hex_format(gas_price),
Wei.hex_format(value)
)
data =
case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do
{:error, %{data: data}} ->
data
_ ->
""
end
revert_reason_parts = String.split(data, "revert: ")
formatted_revert_reason =
if Enum.count(revert_reason_parts) > 1 do
Enum.at(revert_reason_parts, 1)
else
data
end
if byte_size(formatted_revert_reason) > 0 do
transaction
|> Changeset.change(%{revert_reason: formatted_revert_reason})
|> Repo.update()
end
formatted_revert_reason
end
@doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
@ -3499,14 +3565,21 @@ defmodule Explorer.Chain do
|> page_coin_balances(paging_options)
|> Repo.all()
min_block_number =
if Enum.empty?(balances_raw) do
balances_raw
else
balances_raw_filtered =
balances_raw
|> Enum.min_by(fn balance -> balance.block_number end)
|> Enum.filter(fn balance -> balance.value end)
min_block_number =
balances_raw_filtered
|> Enum.min_by(fn balance -> balance.block_number end, fn -> %{} end)
|> Map.get(:block_number)
max_block_number =
balances_raw
|> Enum.max_by(fn balance -> balance.block_number end)
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)
@ -3524,7 +3597,7 @@ defmodule Explorer.Chain do
balances_with_dates =
if blocks_delta > 0 do
balances_raw
balances_raw_filtered
|> Enum.map(fn balance ->
date =
trunc(
@ -3537,7 +3610,7 @@ defmodule Explorer.Chain do
%{balance | block_timestamp: formatted_date}
end)
else
balances_raw
balances_raw_filtered
|> Enum.map(fn balance ->
date = min_block_unix_timestamp
@ -3547,8 +3620,8 @@ defmodule Explorer.Chain do
end
balances_with_dates
|> Enum.filter(fn balance -> balance.value end)
|> Enum.sort(fn balance1, balance2 -> balance1.block_timestamp >= balance2.block_timestamp end)
|> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end)
end
end
def get_coin_balance(address_hash, block_number) do
@ -4266,6 +4339,129 @@ defmodule Explorer.Chain do
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
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do

@ -72,14 +72,19 @@ defmodule Explorer.Chain.Address.CoinBalance do
The last coin balance from an Address is the last block indexed.
"""
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
query =
from(
cb in CoinBalance,
where: cb.address_hash == ^address_hash,
where: not is_nil(cb.value),
order_by: [desc: :block_number],
limit: ^page_size,
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
@doc """
@ -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.
"""
def balances_by_day(address_hash, block_timestamp \\ nil) do
{days_to_consider, _} =
Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days]
|> Integer.parse()
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
|> limit_time_interval(block_timestamp)
|> limit_time_interval(days_to_consider, block_timestamp)
|> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end
def limit_time_interval(query, nil) do
query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'"))
def limit_time_interval(query, days_to_consider, nil) do
query
|> where(
[cb, b],
b.timestamp >=
fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end
def limit_time_interval(query, %{timestamp: timestamp}) do
query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC"))
def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
query
|> where(
[cb, b],
b.timestamp >=
fragment(
"(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
^timestamp,
^"Etc/UTC",
^%Postgrex.Interval{days: days_to_consider}
)
)
end
def last_coin_balance_timestamp(address_hash) do

@ -6,8 +6,8 @@ defmodule Explorer.Chain.Log do
require Logger
alias ABI.{Event, FunctionSelector}
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Repo
@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
@ -119,22 +119,35 @@ defmodule Explorer.Chain.Log do
@doc """
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
with {:ok, selector, mapping} <- find_and_decode(abi, log, transaction),
def decode(log, transaction) do
address_options = [
necessity_by_association: %{
:smart_contract => :optional
}
]
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
def decode(log, transaction) do
defp find_candidates(log, transaction) do
case log.first_topic do
"0x" <> hex_part ->
case Integer.parse(hex_part, 16) do
{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}
@ -145,7 +158,7 @@ defmodule Explorer.Chain.Log do
end
end
defp find_candidates(method_id, log, transaction) do
defp find_candidates_query(method_id, log, transaction) do
candidates_query =
from(
contract_method in ContractMethod,

@ -255,7 +255,8 @@ defmodule Explorer.Chain.SmartContract do
|> prepare_changes(&upsert_contract_methods/1)
end
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error) do
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error, error_message) do
validated =
smart_contract
|> cast(attrs, [
:name,
@ -268,7 +269,12 @@ defmodule Explorer.Chain.SmartContract do
:constructor_arguments
])
|> validate_required([:name, :compiler_version, :optimization, :address_hash])
|> add_error(:contract_source_code, error_message(error))
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
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(: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(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}"
end

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

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

@ -7,6 +7,7 @@ defmodule Explorer.ChainSpec.GenesisData do
require Logger
alias Explorer.ChainSpec.Geth.Importer, as: GethImporter
alias Explorer.ChainSpec.Parity.Importer
alias HTTPoison.Response
@ -58,11 +59,24 @@ defmodule Explorer.ChainSpec.GenesisData do
path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path]
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 ->
case fetch_spec(path) do
{:ok, chain_spec} ->
case variant do
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} ->
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
@moduledoc """
Imports data from parity chain spec.
Imports data from Parity/Open Ethereum chain spec.
"""
require Logger

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

Loading…
Cancel
Save