Merge branch 'master' into vb-pending-txs-api

pull/3145/head
Victor Baranov 4 years ago committed by GitHub
commit 872459f284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .dialyzer-ignore
  2. 24
      CHANGELOG.md
  3. 4
      apps/block_scout_web/assets/css/_helpers.scss
  4. 9
      apps/block_scout_web/assets/css/components/_card.scss
  5. 6
      apps/block_scout_web/assets/css/components/_dropdown.scss
  6. 53
      apps/block_scout_web/assets/css/components/_modal.scss
  7. 23
      apps/block_scout_web/assets/css/components/_modal_status.scss
  8. 15
      apps/block_scout_web/assets/css/components/_navbar.scss
  9. 6
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  10. 258
      apps/block_scout_web/assets/js/lib/modals.js
  11. 142
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  12. 2
      apps/block_scout_web/assets/js/lib/smart_contract/index.js
  13. 53
      apps/block_scout_web/assets/js/lib/smart_contract/read_only_functions.js
  14. 29
      apps/block_scout_web/assets/js/lib/smart_contract/write.js
  15. 1
      apps/block_scout_web/assets/js/pages/write_contract.js
  16. 2203
      apps/block_scout_web/assets/package-lock.json
  17. 4
      apps/block_scout_web/assets/package.json
  18. 1
      apps/block_scout_web/assets/webpack.config.js
  19. 4
      apps/block_scout_web/config/config.exs
  20. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  21. 39
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
  22. 45
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
  23. 39
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
  24. 14
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  25. 71
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  26. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  27. 6
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  28. 21
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  29. 79
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  30. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  31. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/_metatags.html.eex
  32. 19
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex
  33. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/_metatags.html.eex
  34. 20
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex
  35. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/_metatags.html.eex
  36. 20
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex
  37. 31
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_modal_status.html.eex
  38. 1
      apps/block_scout_web/lib/block_scout_web/templates/icons/_external_link.html.eex
  39. 19
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  40. 3
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  41. 37
      apps/block_scout_web/lib/block_scout_web/templates/log/_data_decoded_view.html.eex
  42. 86
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  43. 23
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex
  44. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/read_contract/index.html.eex
  45. 42
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
  46. 80
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  47. 8
      apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
  48. 13
      apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
  49. 22
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  50. 13
      apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
  51. 13
      apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
  52. 22
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  53. 3
      apps/block_scout_web/lib/block_scout_web/views/log_view.ex
  54. 30
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  55. 21
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  56. 136
      apps/block_scout_web/priv/gettext/default.pot
  57. 136
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  58. 79
      apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
  59. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
  60. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
  61. 135
      apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
  62. 19
      apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs
  63. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs
  64. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs
  65. 177
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
  66. 24
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  67. 132
      apps/explorer/lib/explorer/chain.ex
  68. 32
      apps/explorer/lib/explorer/chain/log.ex
  69. 19
      apps/explorer/lib/explorer/chain/supply/token_bridge.ex
  70. 18
      apps/explorer/lib/explorer/chain_spec/genesis_data.ex
  71. 93
      apps/explorer/lib/explorer/chain_spec/geth/importer.ex
  72. 3
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  73. 65
      apps/explorer/lib/explorer/smart_contract/reader.ex
  74. 12
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  75. 57
      apps/explorer/lib/explorer/smart_contract/writer.ex
  76. 35
      apps/explorer/test/explorer/chain/log_test.exs
  77. 145
      apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
  78. 84
      apps/explorer/test/explorer/chain_test.exs
  79. 88
      apps/explorer/test/explorer/smart_contract/reader_test.exs
  80. 329
      apps/explorer/test/explorer/smart_contract/writer_test.exs
  81. 53
      apps/explorer/test/support/fixture/chain_spec/qdai_genesis.json
  82. 6
      docker/Makefile

@ -1,6 +1,7 @@
: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
@ -12,6 +13,7 @@ apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'fa
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:324
lib/block_scout_web/views/layout_view.ex:143
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
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22

@ -1,9 +1,33 @@
## Current
### Features
- [#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
- [#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
- [#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
- [#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

@ -29,3 +29,7 @@
height: 0;
visibility: hidden;
}
.hidden {
display: none!important;
}

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

@ -0,0 +1,142 @@
import $ from 'jquery'
import ethNetProps from 'eth-net-props'
import { walletEnabled, getCurrentAccount } 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 () {
$('[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,29 @@
import Web3 from 'web3'
export const walletEnabled = () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
if (window.ethereum._state && window.ethereum._state.isUnlocked) { // Nifty Wallet
window.web3 = new Web3(window.web3.currentProvider)
return Promise.resolve(true)
} else if (window.ethereum._state && window.ethereum._state.isUnlocked === false) { // 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 getCurrentAccount = async () => {
const accounts = await window.web3.eth.getAccounts()
const account = accounts[0] ? accounts[0].toLowerCase() : null
return account
}

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

File diff suppressed because it is too large Load Diff

@ -25,6 +25,7 @@
"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",
@ -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

@ -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

@ -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.")

@ -2,21 +2,63 @@ 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
)
else
:error ->
@ -33,13 +75,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)})
)

@ -2100,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>

@ -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

@ -103,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,11 @@
<%= if @contract_type == "proxy" do %>
<div>
<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,51 +16,69 @@
&#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"] %>' />
<%= for input <- function["inputs"] do %>
<%= if queryable?(function["inputs"]) do %>
<%= for input <- function["inputs"] do %>
<div class="form-group pr-2">
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm mt-2" placeholder='<%= input["name"] %>(<%= input["type"] %>)' />
</div>
<% end %>
<% end %>
<%= if payable?(function) do %>
<div class="form-group pr-2">
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm mt-2" placeholder='<%= input["name"] %>(<%= input["type"] %>)' />
<input type="text" name="function_input" tx-value class="form-control form-control-sm address-input-sm mt-2" placeholder='value(<%= gettext("ETH")%>)' />
</div>
<% 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>
<div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'>
<%= if (queryable?(function["inputs"])), do: raw "&#8627;" %>
<%= 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;" %>
<%= for output <- function["outputs"] do %>
<%= output["type"] %>
<% end %>
</div>
<%= for output <- function["outputs"] do %>
<%= output["type"] %>
<% end %>
</div>
<% end %>
<div data-function-response></div>
</div>
<% else %>
<span class="py-2">
<%= for output <- function["outputs"] do %>
<%= if address?(output["type"]) do %>
<%= link(
output["value"],
to: address_path(@conn, :show, output["value"])
) %>
<% else %>
<%= if output["type"] == "uint256" do %>
<div data-wei-ether-converter>
<span data-conversion-unit><%= output["value"] %></span>
<span class="py-2 px-2">
<input class="wei-ether" type="checkbox" autocomplete="off">
<span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span>
<span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span>
</span>
</div>
<% else %>
<%= values(output["value"], output["type"]) %>
<% end %>
<%= if outputs?(function["outputs"]) do %>
<%= for output <- function["outputs"] do %>
<%= if address?(output["type"]) do %>
<%= link(
output["value"],
to: address_path(@conn, :show, output["value"])
) %>
<% else %>
<%= if output["type"] == "uint256" do %>
<div data-wei-ether-converter>
<span data-conversion-unit><%= output["value"] %></span>
<span class="py-2 px-2">
<input class="wei-ether" type="checkbox" autocomplete="off">
<span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span>
<span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span>
</span>
</div>
<% else %>
<%= values(output["value"], output["type"]) %>
<% end %>
<% end %>
<% end %>
<% end %>
</span>

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

@ -11,7 +11,7 @@
<div class="card">
<%= 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,41 +15,39 @@
<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">
<%= 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>
<td align=left>
<%= 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>
<% _value -> %>
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text='<%= copy_text %>'
data-placement="top"
data-toggle="tooltip"
style="float: left;height: 20px;"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="25" height="25">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
<pre class="transaction-input-text pre-wrap" style="margin-bottom: 0px;"><code style="line-height: 25px;"><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
<% end %>
</td>
</tr>

@ -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 %>

@ -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

@ -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"]

@ -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,

@ -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 ""
@ -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 ""
@ -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:40
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:75
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 ""
@ -903,7 +898,7 @@ 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:394
@ -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 ""
@ -1011,6 +1003,9 @@ 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 ""
@ -1191,7 +1180,7 @@ 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:44
msgid "Query"
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:145
#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
#: 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:139
#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
#: 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 ""
@ -1509,11 +1498,8 @@ 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 ""
@ -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:74
msgid "WEI"
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 ""
@ -1807,7 +1793,7 @@ 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,7 +1823,7 @@ 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/address_view.ex:343
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,7 +1833,7 @@ 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/address_view.ex:353
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@ -1855,7 +1841,7 @@ 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,7 +1862,7 @@ 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 ""
@ -1913,3 +1899,37 @@ msgstr ""
#: 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:44
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 ""

@ -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 ""
@ -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 ""
@ -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:40
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:75
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 ""
@ -903,7 +898,7 @@ 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:394
@ -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 ""
@ -1011,6 +1003,9 @@ 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 ""
@ -1191,7 +1180,7 @@ 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:44
msgid "Query"
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:145
#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
#: 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:139
#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
#: 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 ""
@ -1509,11 +1498,8 @@ 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 ""
@ -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:74
msgid "WEI"
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 ""
@ -1807,7 +1793,7 @@ 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,7 +1823,7 @@ 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/address_view.ex:343
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,7 +1833,7 @@ 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/address_view.ex:353
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@ -1855,7 +1841,7 @@ 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,7 +1862,7 @@ 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 ""
@ -1913,3 +1899,37 @@ msgstr ""
#: 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:44
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 ""

@ -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

@ -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"

@ -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

@ -28,6 +28,7 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Contract
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.LastFetchedCounter
@ -4338,53 +4339,126 @@ defmodule Explorer.Chain do
end
end
def combine_proxy_implementation_abi(address_hash, abi) when not is_nil(abi) do
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)
implementation_abi =
if implementation_method_abi do
implementation_address =
case Reader.query_contract(address_hash, abi, %{
"implementation" => []
}) do
%{"implementation" => {:ok, [result]}} -> result
_ -> nil
end
if implementation_method_abi, do: true, else: false
end
if implementation_address do
implementation_address_hash_string = "0x" <> Base.encode16(implementation_address, case: :lower)
def is_proxy_contract?(abi) when is_nil(abi) do
false
end
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()
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)
if implementation_smart_contract do
implementation_smart_contract
|> Map.get(:abi)
else
[]
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
_ ->
[]
end
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
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
else
[]
end
end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
[]
end

@ -119,25 +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, address_hash: address_hash}}})
when not is_nil(abi) do
full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
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}
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}
@ -148,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,

@ -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)

@ -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} ->
Importer.import_emission_rewards(chain_spec)
{:ok, _} = Importer.import_genesis_accounts(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

@ -112,6 +112,32 @@ defmodule Explorer.SmartContract.Reader do
end)
end
@spec query_contract(
String.t(),
String.t(),
term(),
functions()
) :: functions_results()
def query_contract(contract_address, from, abi, functions) do
requests =
functions
|> Enum.map(fn {function_name, args} ->
%{
contract_address: contract_address,
from: from,
function_name: function_name,
args: args
}
end)
requests
|> query_contracts(abi)
|> Enum.zip(requests)
|> Enum.into(%{}, fn {response, request} ->
{request.function_name, response}
end)
end
@doc """
Runs batch of contract functions on given addresses for smart contract with an expected ABI and functions.
@ -180,7 +206,21 @@ defmodule Explorer.SmartContract.Reader do
end
end
defp fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
case implementation_abi do
nil ->
[]
_ ->
implementation_abi
|> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view"))
|> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi, contract_address_hash))
end
end
def fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
values =
case function do
%{"inputs" => []} ->
@ -202,26 +242,33 @@ defmodule Explorer.SmartContract.Reader do
@doc """
Fetches the blockchain value of a function that requires arguments.
"""
@spec query_function(String.t(), %{name: String.t(), args: nil}) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: nil}) do
query_function(contract_address_hash, %{name: name, args: []})
@spec query_function(String.t(), %{name: String.t(), args: nil}, atom()) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: nil}, type) do
query_function(contract_address_hash, %{name: name, args: []}, type)
end
@spec query_function(Hash.t(), %{name: String.t(), args: [term()]}) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: args}) do
@spec query_function(Hash.t(), %{name: String.t(), args: [term()]}, atom()) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: args}, type) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
final_abi =
if type == :proxy do
Chain.get_implementation_abi_from_proxy(contract_address_hash, abi)
else
abi
end
outputs =
case abi do
case final_abi do
nil ->
nil
_ ->
function =
abi
final_abi
|> Enum.filter(fn function -> function["name"] == name end)
|> List.first()
@ -229,7 +276,7 @@ defmodule Explorer.SmartContract.Reader do
end
contract_address_hash
|> query_verified_contract(%{name => normalize_args(args)}, abi)
|> query_verified_contract(%{name => normalize_args(args)}, final_abi)
|> link_outputs_and_values(outputs, name)
end

@ -90,9 +90,15 @@ defmodule Explorer.SmartContract.Verifier do
"compiler_version" => generated_compiler_version
} = extract_bytecode_and_metadata_hash(bytecode)
"0x" <> blockchain_created_tx_input =
address_hash
|> Chain.smart_contract_creation_tx_bytecode()
blockchain_created_tx_input =
case Chain.smart_contract_creation_tx_bytecode(address_hash) do
nil ->
bytecode
blockchain_created_tx_input_with_0x ->
"0x" <> blockchain_created_tx_input = blockchain_created_tx_input_with_0x
blockchain_created_tx_input
end
%{
"metadata_hash" => _metadata_hash,

@ -0,0 +1,57 @@
defmodule Explorer.SmartContract.Writer do
@moduledoc """
Generates smart-contract transactions
"""
alias Explorer.Chain
@spec write_functions(Hash.t()) :: [%{}]
def write_functions(contract_address_hash) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
case abi do
nil ->
[]
_ ->
abi
|> filter_write_functions()
end
end
@spec write_functions_proxy(Hash.t()) :: [%{}]
def write_functions_proxy(implementation_address_hash_string) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
case implementation_abi do
nil ->
[]
_ ->
implementation_abi
|> filter_write_functions()
end
end
def write_function?(function) do
!event?(function) && !constructor?(function) &&
(payable?(function) || nonpayable?(function))
end
defp filter_write_functions(abi) do
abi
|> Enum.filter(&write_function?(&1))
end
defp event?(function), do: function["type"] == "event"
defp constructor?(function), do: function["type"] == "constructor"
defp payable?(function), do: function["stateMutability"] == "payable" || function["payable"]
defp nonpayable?(function),
do:
function["stateMutability"] == "nonpayable" ||
(!function["payable"] && !function["constant"] && !function["stateMutability"])
end

@ -58,29 +58,29 @@ defmodule Explorer.Chain.LogTest do
end
test "that a contract call transaction that has a verified contract returns the decoded input data" do
smart_contract =
insert(:smart_contract,
abi: [
%{
"anonymous" => false,
"inputs" => [
%{"indexed" => true, "name" => "_from_human", "type" => "string"},
%{"indexed" => false, "name" => "_number", "type" => "uint256"},
%{"indexed" => true, "name" => "_belly", "type" => "bool"}
],
"name" => "WantsPets",
"type" => "event"
}
]
)
to_address = insert(:address, contract_code: "0x")
insert(:smart_contract,
abi: [
%{
"anonymous" => false,
"inputs" => [
%{"indexed" => true, "name" => "_from_human", "type" => "string"},
%{"indexed" => false, "name" => "_number", "type" => "uint256"},
%{"indexed" => true, "name" => "_belly", "type" => "bool"}
],
"name" => "WantsPets",
"type" => "event"
}
],
address_hash: to_address.hash
)
topic1 = "0x" <> Base.encode16(:keccakf1600.hash(:sha3_256, "WantsPets(string,uint256,bool)"), case: :lower)
topic2 = "0x" <> Base.encode16(:keccakf1600.hash(:sha3_256, "bob"), case: :lower)
topic3 = "0x0000000000000000000000000000000000000000000000000000000000000001"
data = "0x0000000000000000000000000000000000000000000000000000000000000000"
to_address = insert(:address, smart_contract: smart_contract)
transaction =
:transaction_to_verified_contract
|> insert(to_address: to_address)
@ -88,6 +88,7 @@ defmodule Explorer.Chain.LogTest do
log =
insert(:log,
address: to_address,
transaction: transaction,
first_topic: topic1,
second_topic: topic2,

File diff suppressed because one or more lines are too long

@ -5194,7 +5194,7 @@ defmodule Explorer.ChainTest do
end
end
describe "combine_proxy_implementation_abi/2" do
describe "proxy contracts features" do
@proxy_abi [
%{
"type" => "function",
@ -5316,23 +5316,23 @@ defmodule Explorer.ChainTest do
}
]
test "returns empty [] abi if proxy abi is null" do
test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == []
end
test "returns [] abi for unverified proxy" do
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "returns proxy abi if implementation is not verified" do
test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi
end
test "returns proxy + implementation abi if implementation is verified" do
test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
@ -5364,5 +5364,79 @@ defmodule Explorer.ChainTest do
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
end
test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == []
end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == []
end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi)
assert implementation_abi == @implementation_abi
end
test "get_implementation_abi/1 returns empty [] abi if implmentation address is null" do
assert Chain.get_implementation_abi(nil) == []
end
test "get_implementation_abi/1 returns [] if implementation is not verified" do
implementation_contract_address = insert(:contract_address)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == []
end
test "get_implementation_abi/1 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string)
assert implementation_abi == @implementation_abi
end
end
end

@ -169,7 +169,89 @@ defmodule Explorer.SmartContract.ReaderTest do
end
end
describe "query_function/2" do
describe "read_only_functions_proxy/1" do
test "fetches the smart contract proxy read only functions with the blockchain value" do
proxy_smart_contract =
insert(:smart_contract,
abi: [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [
%{
"type" => "address",
"name" => ""
}
],
"name" => "implementation",
"inputs" => [],
"constant" => true
}
]
)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
"outputs" => [%{"name" => "", "type" => "bool"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
blockchain_get_function_mock()
response =
Reader.read_only_functions_proxy(
proxy_smart_contract.address_hash,
"0x" <> implementation_contract_address_hash_string
)
assert [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}],
"payable" => _,
"stateMutability" => _,
"type" => _
},
%{
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
"outputs" => [%{"name" => "", "type" => "bool", "value" => ""}],
"payable" => _,
"stateMutability" => _,
"type" => _
}
] = response
end
end
describe "query_function/3" do
test "given the arguments, fetches the function value from the blockchain" do
smart_contract = insert(:smart_contract)
@ -181,7 +263,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"type" => "uint256",
"value" => 0
}
] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: []})
] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: []}, :regular)
end
test "nil arguments is treated as []" do
@ -195,7 +277,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"type" => "uint256",
"value" => 0
}
] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: nil})
] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: nil}, :regular)
end
end

@ -0,0 +1,329 @@
defmodule Explorer.SmartContract.WriterTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
import Mox
alias Explorer.SmartContract.Writer
@abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "upgradeabilityOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
},
%{"type" => "fallback", "stateMutability" => "payable", "payable" => true},
%{
"type" => "event",
"name" => "ProxyOwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => false},
%{"type" => "address", "name" => "newOwner", "indexed" => false}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
}
]
@implementation_abi [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "extraReceiverAmount",
"inputs" => [%{"type" => "address", "name" => "_receiver"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "bridgesAllowedLength",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "pure",
"payable" => false,
"outputs" => [%{"type" => "bytes4", "name" => ""}],
"name" => "blockRewardContractId",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedForAccountInBlock",
"inputs" => [%{"type" => "address", "name" => "_account"}, %{"type" => "uint256", "name" => "_blockNumber"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedForAccount",
"inputs" => [%{"type" => "address", "name" => "_account"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedInBlock",
"inputs" => [%{"type" => "uint256", "name" => "_blockNumber"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedTotally",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "pure",
"payable" => false,
"outputs" => [%{"type" => "address[1]", "name" => ""}],
"name" => "bridgesAllowed",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "addExtraReceiver",
"inputs" => [%{"type" => "uint256", "name" => "_amount"}, %{"type" => "address", "name" => "_receiver"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedTotallyByBridge",
"inputs" => [%{"type" => "address", "name" => "_bridge"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "extraReceiverByIndex",
"inputs" => [%{"type" => "uint256", "name" => "_index"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "bridgeAmount",
"inputs" => [%{"type" => "address", "name" => "_bridge"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "extraReceiversLength",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
"name" => "reward",
"inputs" => [%{"type" => "address[]", "name" => "benefactors"}, %{"type" => "uint16[]", "name" => "kind"}],
"constant" => false
},
%{
"type" => "event",
"name" => "AddedReceiver",
"inputs" => [
%{"type" => "uint256", "name" => "amount", "indexed" => false},
%{"type" => "address", "name" => "receiver", "indexed" => true},
%{"type" => "address", "name" => "bridge", "indexed" => true}
],
"anonymous" => false
}
]
doctest Explorer.SmartContract.Writer
setup :verify_on_exit!
describe "write_functions/1" do
test "fetches the smart contract write functions" do
smart_contract =
insert(
:smart_contract,
abi: @abi
)
response = Writer.write_functions(smart_contract.address_hash)
assert [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
},
%{"type" => "fallback", "stateMutability" => "payable", "payable" => true}
] = response
end
end
describe "write_functions_proxy/1" do
test "fetches the smart contract proxy write functions" do
proxy_smart_contract =
insert(:smart_contract,
abi: @abi
)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
response = Writer.write_functions_proxy("0x" <> implementation_contract_address_hash_string)
assert [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "addExtraReceiver",
"inputs" => [
%{"type" => "uint256", "name" => "_amount"},
%{"type" => "address", "name" => "_receiver"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
"name" => "reward",
"inputs" => [
%{"type" => "address[]", "name" => "benefactors"},
%{"type" => "uint16[]", "name" => "kind"}
],
"constant" => false
}
] = response
end
end
end

File diff suppressed because one or more lines are too long

@ -220,6 +220,12 @@ endif
ifdef TXS_STATS_DAYS_TO_COMPILE_AT_INIT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_STATS_DAYS_TO_COMPILE_AT_INIT=$(TXS_STATS_DAYS_TO_COMPILE_AT_INIT)'
endif
ifdef APPS_MENU
BLOCKSCOUT_CONTAINER_PARAMS += -e 'APPS_MENU=$(APPS_MENU)'
endif
ifdef EXTERNAL_APPS
BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXTERNAL_APPS=$(EXTERNAL_APPS)'
endif
HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep ${DOCKER_IMAGE})
build:

Loading…
Cancel
Save