Merge branch 'master' into split-api-and-webapp-routes

pull/2376/head
saneery 5 years ago
commit 524c7cd78c
  1. 11
      CHANGELOG.md
  2. 1
      apps/block_scout_web/assets/__tests__/pages/pending_transactions.js
  3. 37
      apps/block_scout_web/assets/css/components/_stakes_table.scss
  4. 214
      apps/block_scout_web/assets/css/components/_tile.scss
  5. 7
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  6. 4
      apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js
  7. 29
      apps/block_scout_web/assets/js/lib/list_morph.js
  8. 6
      apps/block_scout_web/assets/js/lib/market_history_chart.js
  9. 10
      apps/block_scout_web/assets/js/lib/redux_helpers.js
  10. 17
      apps/block_scout_web/assets/js/lib/utils.js
  11. 4
      apps/block_scout_web/assets/js/pages/address.js
  12. 4
      apps/block_scout_web/assets/js/pages/address/coin_balances.js
  13. 4
      apps/block_scout_web/assets/js/pages/address/internal_transactions.js
  14. 4
      apps/block_scout_web/assets/js/pages/address/logs.js
  15. 4
      apps/block_scout_web/assets/js/pages/address/transactions.js
  16. 4
      apps/block_scout_web/assets/js/pages/address/validations.js
  17. 19
      apps/block_scout_web/assets/js/pages/blocks.js
  18. 28
      apps/block_scout_web/assets/js/pages/chain.js
  19. 4
      apps/block_scout_web/assets/js/pages/pending_transactions.js
  20. 4
      apps/block_scout_web/assets/js/pages/transaction.js
  21. 4
      apps/block_scout_web/assets/js/pages/transactions.js
  22. 4
      apps/block_scout_web/assets/js/pages/verification_form.js
  23. 2
      apps/block_scout_web/assets/webpack.config.js
  24. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  25. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  26. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  27. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  28. 5
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
  29. 329
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex
  30. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  31. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
  32. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
  33. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex
  34. 4
      apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
  35. 12
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  36. 4
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  37. 6
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
  38. 8
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
  39. 6
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
  40. 2
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
  41. 9
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
  42. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  43. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
  44. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex
  45. 18
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  46. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  47. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  48. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  49. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  50. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  51. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
  52. 4
      apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex
  53. 4
      apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
  54. 167
      apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex
  55. 12
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  56. 85
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
  57. 72
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
  58. 9
      apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
  59. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
  60. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
  61. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex
  62. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
  63. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
  64. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex
  65. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex
  66. 38
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  67. 54
      apps/block_scout_web/priv/gettext/default.pot
  68. 54
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  69. 51
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
  70. 18
      apps/explorer/config/config.exs
  71. 294
      apps/explorer/lib/explorer/chain.ex
  72. 3
      apps/explorer/lib/explorer/chain/transaction.ex
  73. 2
      apps/explorer/lib/explorer/counters/average_block_time.ex
  74. 371
      apps/explorer/lib/explorer/eth_rpc.ex
  75. 35
      apps/explorer/lib/explorer/etherscan/logs.ex
  76. 2
      apps/explorer/lib/explorer/market/market_history_cache.ex
  77. 82
      apps/explorer/lib/explorer/smart_contract/reader.ex
  78. 70
      apps/explorer/test/explorer/chain_test.exs
  79. 40
      apps/explorer/test/explorer/etherscan/logs_test.exs
  80. 2
      apps/explorer/test/explorer/smart_contract/reader_test.exs

@ -2,12 +2,21 @@
### Features ### Features
- [#2376](https://github.com/poanetwork/blockscout/pull/2376) - Split API and WebApp routes - [#2376](https://github.com/poanetwork/blockscout/pull/2376) - Split API and WebApp routes
- [#2391](https://github.com/poanetwork/blockscout/pull/2391) - Controllers Improvements
- [#2379](https://github.com/poanetwork/blockscout/pull/2379) - Disable network selector when is empty - [#2379](https://github.com/poanetwork/blockscout/pull/2379) - Disable network selector when is empty
- [#2374](https://github.com/poanetwork/blockscout/pull/2374) - decode constructor arguments for verified smart contracts
- [#2366](https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs
- [#2360](https://github.com/poanetwork/blockscout/pull/2360) - add default evm version to smart contract verification - [#2360](https://github.com/poanetwork/blockscout/pull/2360) - add default evm version to smart contract verification
- [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions - [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions
- [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint
- [#2324](https://github.com/poanetwork/blockscout/pull/2324) - set timeout for loading message on the main page
### Fixes ### Fixes
- [#2410](https://github.com/poanetwork/blockscout/pull/2410) - preload smart contract for logs decoding
- [#2405](https://github.com/poanetwork/blockscout/pull/2405) - added templates for table loader and tile loader
- [#2398](https://github.com/poanetwork/blockscout/pull/2398) - show only one decoded candidate
- [#2395](https://github.com/poanetwork/blockscout/pull/2395) - new block loading animation
- [#2389](https://github.com/poanetwork/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used)
- [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css - [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css
- [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info - [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info
- [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict - [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict
@ -25,6 +34,8 @@
- [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments - [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments
### Chore ### Chore
- [#2418](https://github.com/poanetwork/blockscout/pull/2418) - Remove parentheses in market cap percentage
- [#2401](https://github.com/poanetwork/blockscout/pull/2401) - add ENV vars to manage updating period of average block time and market history cache
- [#2363](https://github.com/poanetwork/blockscout/pull/2363) - add parameters example for eth rpc - [#2363](https://github.com/poanetwork/blockscout/pull/2363) - add parameters example for eth rpc
- [#2342](https://github.com/poanetwork/blockscout/pull/2342) - Upgrade Postgres image version in Docker setup - [#2342](https://github.com/poanetwork/blockscout/pull/2342) - Upgrade Postgres image version in Docker setup
- [#2325](https://github.com/poanetwork/blockscout/pull/2325) - Reduce function input to address' hash only where possible - [#2325](https://github.com/poanetwork/blockscout/pull/2325) - Reduce function input to address' hash only where possible

@ -1,4 +1,3 @@
import _ from 'lodash'
import { reducer, initialState } from '../../js/pages/pending_transactions' import { reducer, initialState } from '../../js/pages/pending_transactions'
test('CHANNEL_DISCONNECTED', () => { test('CHANNEL_DISCONNECTED', () => {

@ -26,6 +26,43 @@ $stakes-table-cell-separation: 25px !default;
} }
} }
// Loader
.table-content-loader {
display: inline-block;
height: 24px;
width: 100%;
border-radius: 4px;
background-color: #f5f6fa;
overflow: hidden;
position: relative;
&:before {
width: inherit;
height: inherit;
content: '';
position: absolute;
background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
animation-duration: 1.7s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-name: placeholderAnimate;
background-size: 1300px;
}
}
@keyframes placeholderAnimate {
0%{ background-position: -650px 0; }
100% { background-position: 650px 0; }
}
.table-content-pseudo {
td {
&:last-child {
padding-right: 24px !important;
}
}
}
.stakes-table { .stakes-table {
min-width: fit-content; min-width: fit-content;
width: 100%; width: 100%;

@ -339,9 +339,6 @@ $tile-body-a-color: #5959d8 !default;
padding-left: 6px; padding-left: 6px;
padding-right: 6px; padding-right: 6px;
} }
.tile-type-block {
overflow: hidden;
}
} }
.row { .row {
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
@ -351,3 +348,214 @@ $tile-body-a-color: #5959d8 !default;
} }
} }
} }
// Loader
.tile-type-loading {
background-color: #fff;
padding-top: 30px;
padding-bottom: 28px;
}
.tile-loader {
display: inline-block;
height: 20px;
width: 100%;
border-radius: 4px;
background-color: #f5f6fa;
overflow: hidden;
position: relative;
&:before {
width: inherit;
height: inherit;
content: '';
position: absolute;
background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
animation-duration: 1.7s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-name: tilePlaceholderAnimate;
background-size: 1300px;
}
}
.tile-label-loader {
height: 14px;
width: 80px;
}
.tile-address-loader {
& + .tile-address-loader {
margin-top: 6px;
}
}
@keyframes tilePlaceholderAnimate {
0%{ background-position: -650px 0; }
100% { background-position: 650px 0; }
}
// Loading Animation
@keyframes playBlockLoadingAnimation {
0%, 90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
[data-selector="chain-block-list"] {
.col-lg-3:first-child {
.tile-type-block-animation {
animation: playBlockLoadingAnimation 2.1s linear forwards;
}
}
}
.fade-up-blocks-chain {
.tile-type-block {
position: relative;
}
.tile-type-block-animation {
opacity: 0;
position: absolute;
top: -1px;
left: -4px;
width: calc(100% + 5px);
height: calc(100% + 2px);
background-color: #F6F7F9;
border-radius: 4px;
overflow: hidden;
transition: .24s ease-out;
border-top: 1px solid #dee2e6;
border-right: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
pointer-events: none;
.tile-type-line-up {
position: absolute;
bottom: -1px;
left: 0;
height: calc(100% + 2px);
width: 4px;
background-color: $tile-type-block-color;
transform: scaleY(0);
transform-origin: center bottom;
animation: blockLoaderLine 2s linear forwards;
z-index: 2;
}
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 1px;
background-color: #dee2e6;
}
}
}
.cube-animation-title {
font-size: 12px;
color: #a3a9b5;
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
.fade-up-blocks-chain:first-child {
.tile-type-block-animation {
opacity: 1;
}
}
@keyframes blockLoaderLine {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
$cube-bezier: cubic-bezier(.25,.8,.25,1);
$cube-quantity: 5;
.cube-animation-wrapper {
width: 560px;
height: 290px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.26);
svg {
width: 50px;
margin-top: -29px;
.side {
fill: $tile-type-block-color;
opacity: 1;
&:nth-of-type(2) {
fill: lighten($tile-type-block-color, 30);
opacity: 0.5;
}
&:nth-of-type(3) {
fill: lighten($tile-type-block-color, 80);
opacity: 0.5;
}
}
}
@while $cube-quantity > 0 {
.cube-animation-row:nth-of-type(#{$cube-quantity}) {
left: 25px * $cube-quantity;
top: 15px * $cube-quantity;
}
.cube-animation-column:nth-of-type(#{$cube-quantity}) {
position: relative;
top: 14px * $cube-quantity;
left: 25px * $cube-quantity;
}
.cube-animation-column:nth-of-type(#{$cube-quantity}) svg {
transform: translate3d(0,0,0);
animation: shrink-expand 2.8s $cube-bezier forwards;
animation-delay: -0.16s * $cube-quantity;
}
$cube-quantity: $cube-quantity - 1;
}
}
.cube-animation-center {
position: absolute;
top: 6%;
left: 20%;
}
.cube-animation-row {
display: flex;
flex-direction: row-reverse;
position: absolute;
}
.cube-animation-column {
display: flex;
flex-direction: column-reverse;
}
@keyframes shrink-expand {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}

@ -1,5 +1,6 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import map from 'lodash/map'
import merge from 'lodash/merge'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import listMorph from '../lib/list_morph' import listMorph from '../lib/list_morph'
@ -164,7 +165,7 @@ export const elements = {
if (state.itemKey) { if (state.itemKey) {
const container = $el[0] const container = $el[0]
const newElements = _.map(state.items, (item) => $(item)[0]) const newElements = map(state.items, (item) => $(item)[0])
listMorph(container, newElements, { key: state.itemKey }) listMorph(container, newElements, { key: state.itemKey })
return return
} }
@ -244,7 +245,7 @@ export const elements = {
* adding or removing with the correct animation. Check list_morph.js for more informantion. * adding or removing with the correct animation. Check list_morph.js for more informantion.
*/ */
export function createAsyncLoadStore (reducer, initialState, itemKey) { export function createAsyncLoadStore (reducer, initialState, itemKey) {
const state = _.merge(asyncInitialState, initialState) const state = merge(asyncInitialState, initialState)
const store = createStore(reduceReducers(asyncReducer, reducer, state)) const store = createStore(reduceReducers(asyncReducer, reducer, state))
if (typeof itemKey !== 'undefined') { if (typeof itemKey !== 'undefined') {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import { connectElements } from './redux_helpers.js' import { connectElements } from './redux_helpers.js'
@ -12,7 +12,7 @@ const initialState = {
function infiniteScrollReducer (state = initialState, action) { function infiniteScrollReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'INFINITE_SCROLL_ELEMENTS_LOAD': { case 'INFINITE_SCROLL_ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'LOADING_NEXT_PAGE': { case 'LOADING_NEXT_PAGE': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -1,5 +1,10 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import map from 'lodash/map'
import get from 'lodash/get'
import noop from 'lodash/noop'
import find from 'lodash/find'
import intersectionBy from 'lodash/intersectionBy'
import differenceBy from 'lodash/differenceBy'
import morph from 'nanomorph' import morph from 'nanomorph'
import { updateAllAges } from './from_now' import { updateAllAges } from './from_now'
@ -25,12 +30,12 @@ import { updateAllAges } from './from_now'
export default function (container, newElements, { key, horizontal } = {}) { export default function (container, newElements, { key, horizontal } = {}) {
if (!container) return if (!container) return
const oldElements = $(container).children().get() const oldElements = $(container).children().get()
let currentList = _.map(oldElements, (el) => ({ id: _.get(el, key), el })) let currentList = map(oldElements, (el) => ({ id: get(el, key), el }))
const newList = _.map(newElements, (el) => ({ id: _.get(el, key), el })) const newList = map(newElements, (el) => ({ id: get(el, key), el }))
const overlap = _.intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] })) const overlap = intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] }))
// remove old items // remove old items
const removals = _.differenceBy(currentList, newList, 'id') const removals = differenceBy(currentList, newList, 'id')
let canAnimate = !horizontal && removals.length <= 1 let canAnimate = !horizontal && removals.length <= 1
removals.forEach(({ el }) => { removals.forEach(({ el }) => {
if (!canAnimate) return el.remove() if (!canAnimate) return el.remove()
@ -38,7 +43,7 @@ export default function (container, newElements, { key, horizontal } = {}) {
$el.addClass('shrink-out') $el.addClass('shrink-out')
setTimeout(() => { slideUpRemove($el) }, 400) setTimeout(() => { slideUpRemove($el) }, 400)
}) })
currentList = _.differenceBy(currentList, removals, 'id') currentList = differenceBy(currentList, removals, 'id')
// update kept items // update kept items
currentList = currentList.map(({ el }, i) => ({ currentList = currentList.map(({ el }, i) => ({
@ -47,14 +52,14 @@ export default function (container, newElements, { key, horizontal } = {}) {
})) }))
// add new items // add new items
const finalList = newList.map(({ id, el }) => _.get(_.find(currentList, { id }), 'el', el)).reverse() const finalList = newList.map(({ id, el }) => get(find(currentList, { id }), 'el', el)).reverse()
canAnimate = !horizontal canAnimate = !horizontal
finalList.forEach((el, i) => { finalList.forEach((el, i) => {
if (el.parentElement) return if (el.parentElement) return
if (!canAnimate) return container.insertBefore(el, _.get(finalList, `[${i - 1}]`)) if (!canAnimate) return container.insertBefore(el, get(finalList, `[${i - 1}]`))
canAnimate = false canAnimate = false
if (!_.get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el) if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el)
slideDownBefore($(_.get(finalList, `[${i - 1}]`)), el) slideDownBefore($(get(finalList, `[${i - 1}]`)), el)
}) })
} }
@ -80,7 +85,7 @@ function slideUpRemove ($el) {
}) })
} }
function smarterSlideDown ($el, { insert = _.noop } = {}) { function smarterSlideDown ($el, { insert = noop } = {}) {
if (!$el.length) return if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY const scrollPosition = window.scrollY
@ -100,7 +105,7 @@ function smarterSlideDown ($el, { insert = _.noop } = {}) {
} }
} }
function smarterSlideUp ($el, { complete = _.noop } = {}) { function smarterSlideUp ($el, { complete = noop } = {}) {
if (!$el.length) return if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY const scrollPosition = window.scrollY

@ -4,6 +4,7 @@ import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import { formatUsdValue } from '../lib/currency' import { formatUsdValue } from '../lib/currency'
import sassVariables from '../../css/app.scss' import sassVariables from '../../css/app.scss'
import { showLoader } from '../lib/utils'
const config = { const config = {
type: 'line', type: 'line',
@ -129,6 +130,10 @@ class MarketHistoryChart {
export function createMarketHistoryChart (el) { export function createMarketHistoryChart (el) {
const dataPath = el.dataset.market_history_chart_path const dataPath = el.dataset.market_history_chart_path
const $chartLoading = $('[data-chart-loading-message]') const $chartLoading = $('[data-chart-loading-message]')
const isTimeout = true
const timeoutID = showLoader(isTimeout, $chartLoading)
const $chartError = $('[data-chart-error-message]') const $chartError = $('[data-chart-error-message]')
const chart = new MarketHistoryChart(el, 0, []) const chart = new MarketHistoryChart(el, 0, [])
$.getJSON(dataPath, {type: 'JSON'}) $.getJSON(dataPath, {type: 'JSON'})
@ -143,6 +148,7 @@ export function createMarketHistoryChart (el) {
}) })
.always(() => { .always(() => {
$chartLoading.hide() $chartLoading.hide()
clearTimeout(timeoutID)
}) })
return chart return chart
} }

@ -1,5 +1,7 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import reduce from 'lodash/reduce'
import isObject from 'lodash/isObject'
import forIn from 'lodash/forIn'
import { createStore as reduxCreateStore } from 'redux' import { createStore as reduxCreateStore } from 'redux'
/** /**
@ -97,17 +99,17 @@ export function createStore (reducer) {
*/ */
export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) { export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) {
function loadElements () { function loadElements () {
return _.reduce(elements, (pageLoadParams, { load }, selector) => { return reduce(elements, (pageLoadParams, { load }, selector) => {
if (!load) return pageLoadParams if (!load) return pageLoadParams
const $el = $(selector) const $el = $(selector)
if (!$el.length) return pageLoadParams if (!$el.length) return pageLoadParams
const morePageLoadParams = load($el, store) const morePageLoadParams = load($el, store)
return _.isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams
}, {}) }, {})
} }
function renderElements (state, oldState) { function renderElements (state, oldState) {
_.forIn(elements, ({ render }, selector) => { forIn(elements, ({ render }, selector) => {
if (!render) return if (!render) return
const $el = $(selector) const $el = $(selector)
if (!$el.length) return if (!$el.length) return

@ -1,8 +1,8 @@
import _ from 'lodash' import debounce from 'lodash/debounce'
export function batchChannel (func) { export function batchChannel (func) {
let msgs = [] let msgs = []
const debouncedFunc = _.debounce(() => { const debouncedFunc = debounce(() => {
func.apply(this, [msgs]) func.apply(this, [msgs])
msgs = [] msgs = []
}, 1000, { maxWait: 5000 }) }, 1000, { maxWait: 5000 })
@ -11,3 +11,16 @@ export function batchChannel (func) {
debouncedFunc() debouncedFunc()
} }
} }
export function showLoader (isTimeout, loader) {
if (isTimeout) {
const timeout = setTimeout(function () {
loader.removeAttr('hidden')
loader.show()
}, 1000)
return timeout
} else {
loader.hide()
return null
}
}

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
@ -25,7 +25,7 @@ export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import socket from '../../socket' import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
@ -14,7 +14,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../../socket' import socket from '../../socket'
@ -20,7 +20,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load' import { createAsyncLoadStore } from '../../lib/async_listing_load'
@ -13,7 +13,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'START_SEARCH': { case 'START_SEARCH': {
return Object.assign({}, state, {pagesStack: [], isSearch: true}) return Object.assign({}, state, {pagesStack: [], isSearch: true})

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import { subscribeChannel } from '../../socket' import { subscribeChannel } from '../../socket'
@ -16,7 +16,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import socket from '../../socket' import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
@ -14,7 +14,7 @@ export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { channelDisconnected: true }) return Object.assign({}, state, { channelDisconnected: true })

@ -1,5 +1,10 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import last from 'lodash/last'
import min from 'lodash/min'
import max from 'lodash/max'
import keys from 'lodash/keys'
import rangeRight from 'lodash/rangeRight'
import humps from 'humps' import humps from 'humps'
import socket from '../socket' import socket from '../socket'
import { connectElements } from '../lib/redux_helpers.js' import { connectElements } from '../lib/redux_helpers.js'
@ -14,7 +19,7 @@ export const blockReducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) { function baseReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {
@ -25,7 +30,7 @@ function baseReducer (state = initialState, action) {
if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state
const blockNumber = getBlockNumber(action.msg.blockHtml) const blockNumber = getBlockNumber(action.msg.blockHtml)
const minBlock = getBlockNumber(_.last(state.items)) const minBlock = getBlockNumber(last(state.items))
if (state.items.length && blockNumber < minBlock) return state if (state.items.length && blockNumber < minBlock) return state
@ -62,12 +67,12 @@ function withMissingBlocks (reducer) {
return acc return acc
}, {}) }, {})
const blockNumbers = _(blockNumbersToItems).keys().map(x => parseInt(x, 10)).value() const blockNumbers = keys(blockNumbersToItems).map(x => parseInt(x, 10))
const minBlock = _.min(blockNumbers) const minBlock = min(blockNumbers)
const maxBlock = _.max(blockNumbers) const maxBlock = max(blockNumbers)
return Object.assign({}, result, { return Object.assign({}, result, {
items: _.rangeRight(minBlock, maxBlock + 1) items: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber)) .map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber))
}) })
} }

@ -1,11 +1,15 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import first from 'lodash/first'
import rangeRight from 'lodash/rangeRight'
import find from 'lodash/find'
import map from 'lodash/map'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
import { exchangeRateChannel, formatUsdValue } from '../lib/currency' import { exchangeRateChannel, formatUsdValue } from '../lib/currency'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import { batchChannel } from '../lib/utils' import { batchChannel, showLoader } from '../lib/utils'
import listMorph from '../lib/list_morph' import listMorph from '../lib/list_morph'
import { createMarketHistoryChart } from '../lib/market_history_chart' import { createMarketHistoryChart } from '../lib/market_history_chart'
@ -33,7 +37,7 @@ export const reducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) { function baseReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'RECEIVED_NEW_ADDRESS_COUNT': { case 'RECEIVED_NEW_ADDRESS_COUNT': {
return Object.assign({}, state, { return Object.assign({}, state, {
@ -122,12 +126,12 @@ function withMissingBlocks (reducer) {
if (!result.blocks || result.blocks.length < 2) return result if (!result.blocks || result.blocks.length < 2) return result
const maxBlock = _.first(result.blocks).blockNumber const maxBlock = first(result.blocks).blockNumber
const minBlock = maxBlock - (result.blocks.length - 1) const minBlock = maxBlock - (result.blocks.length - 1)
return Object.assign({}, result, { return Object.assign({}, result, {
blocks: _.rangeRight(minBlock, maxBlock + 1) blocks: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => _.find(result.blocks, ['blockNumber', blockNumber]) || { .map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || {
blockNumber, blockNumber,
chainBlockHtml: placeHolderBlock(blockNumber) chainBlockHtml: placeHolderBlock(blockNumber)
}) })
@ -194,7 +198,7 @@ const elements = {
const container = $el[0] const container = $el[0]
if (state.blocksLoading === false) { if (state.blocksLoading === false) {
const blocks = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0]) const blocks = map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true }) listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true })
} }
} }
@ -210,11 +214,7 @@ const elements = {
}, },
'[data-selector="chain-block-list"] [data-selector="loading-message"]': { '[data-selector="chain-block-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) { render ($el, state, oldState) {
if (state.blocksLoading) { showLoader(state.blocksLoading, $el)
$el.show()
} else {
$el.hide()
}
} }
}, },
'[data-selector="transactions-list"] [data-selector="error-message"]': { '[data-selector="transactions-list"] [data-selector="error-message"]': {
@ -224,7 +224,7 @@ const elements = {
}, },
'[data-selector="transactions-list"] [data-selector="loading-message"]': { '[data-selector="transactions-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) { render ($el, state, oldState) {
$el.toggle(state.transactionsLoading) showLoader(state.transactionsLoading, $el)
} }
}, },
'[data-selector="transactions-list"]': { '[data-selector="transactions-list"]': {
@ -234,7 +234,7 @@ const elements = {
render ($el, state, oldState) { render ($el, state, oldState) {
if (oldState.transactions === state.transactions) return if (oldState.transactions === state.transactions) return
const container = $el[0] const container = $el[0]
const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) const newElements = map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.identifierHash' }) listMorph(container, newElements, { key: 'dataset.identifierHash' })
} }
}, },

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -20,7 +20,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -13,7 +13,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'RECEIVED_NEW_BLOCK': { case 'RECEIVED_NEW_BLOCK': {
if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -18,7 +18,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import { subscribeChannel } from '../socket' import { subscribeChannel } from '../socket'
@ -15,7 +15,7 @@ export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,7 +1,7 @@
const path = require('path'); const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const { ContextReplacementPlugin } = require('webpack') const { ContextReplacementPlugin } = require('webpack');
const glob = require("glob"); const glob = require("glob");
function transpileViewScript(file) { function transpileViewScript(file) {

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false) do :ok <- Chain.check_address_exists(address_hash) do
full_options = paging_options(params) full_options = paging_options(params)
coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options) coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options)
@ -32,7 +32,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
address_coin_balance_path( address_coin_balance_path(
conn, conn,
:index, :index,
address, address_hash,
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -52,7 +52,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
:error -> :error ->
unprocessable_entity(conn) unprocessable_entity(conn)
{:error, :not_found} -> :not_found ->
not_found(conn) not_found(conn)
end end
end end

@ -8,8 +8,18 @@ defmodule BlockScoutWeb.AddressContractController do
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do 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), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render( render(
conn, conn,
"index.html", "index.html",

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressLogsController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false) do :ok <- Chain.check_address_exists(address_hash) do
logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params)) logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one) {results, next_page} = split_list_by_page(logs_plus_one)
@ -26,7 +26,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil nil
next_page_params -> next_page_params ->
address_logs_path(conn, :index, address, Map.delete(next_page_params, "type")) address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end end
items = items =
@ -74,7 +74,7 @@ defmodule BlockScoutWeb.AddressLogsController do
def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false) do :ok <- Chain.check_address_exists(address_hash) do
topic = String.trim(topic) topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil nil
next_page_params -> next_page_params ->
address_logs_path(conn, :index, address, Map.delete(next_page_params, "type")) address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end end
items = items =

@ -15,8 +15,18 @@ defmodule BlockScoutWeb.AddressReadContractController do
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do 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), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render( render(
conn, conn,
"index.html", "index.html",

@ -8,9 +8,8 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
def getblockreward(conn, params) do def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
{:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number), {:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number),
block_options = [necessity_by_association: %{transactions: :optional}], {:ok, block} <- Chain.number_to_block(block_number) do
{:ok, block} <- Chain.number_to_block(block_number, block_options) do reward = Chain.block_reward(block_number)
reward = Chain.block_reward(block)
render(conn, :block_reward, block: block, reward: reward) render(conn, :block_reward, block: block, reward: reward)
else else

@ -1,44 +1,10 @@
defmodule BlockScoutWeb.API.RPC.EthController do defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Ecto.Type, as: EctoType alias Explorer.EthRPC
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
alias Explorer.Etherscan.Logs
@methods %{
"eth_getBalance" => %{
action: :eth_get_balance,
notes: """
the `earliest` parameter will not work as expected currently, because genesis block balances
are not currently imported
""",
example: """
{"id": 0, "jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0x0000000000000000000000000000000000000007", "2"]}
"""
},
"eth_getLogs" => %{
action: :eth_get_logs,
notes: """
Will never return more than 1000 log entries.
""",
example: """
{"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"address": "0x0000000000000000000000000000000000000026","topics": ["0x01"]}]}
"""
}
}
@index_to_word %{
0 => "first",
1 => "second",
2 => "third",
3 => "fourth"
}
def methods, do: @methods
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
responses = responses(requests) responses = EthRPC.responses(requests)
conn conn
|> put_status(200) |> put_status(200)
@ -46,7 +12,7 @@ defmodule BlockScoutWeb.API.RPC.EthController do
end end
def eth_request(%{body_params: %{"_json" => request}} = conn, _) do def eth_request(%{body_params: %{"_json" => request}} = conn, _) do
[response] = responses([request]) [response] = EthRPC.responses([request])
conn conn
|> put_status(200) |> put_status(200)
@ -65,297 +31,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do
_ -> request _ -> request
end end
[response] = responses([decoded_request]) [response] = EthRPC.responses([decoded_request])
conn conn
|> put_status(200) |> put_status(200)
|> render("response.json", %{response: response}) |> render("response.json", %{response: response})
end end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
end
end
def eth_get_logs(filter_options) do
with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
{:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
{:ok, from_block} <- cast_block(from_block_param),
{:ok, to_block} <- cast_block(to_block_param) do
filter =
address_or_topic_params
|> Map.put(:from_block, from_block)
|> Map.put(:to_block, to_block)
|> Map.put(:allow_non_consensus, true)
{:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)}
else
{:error, message} when is_bitstring(message) ->
{:error, message}
{:error, :empty} ->
{:ok, []}
_ ->
{:error, "Something went wrong."}
end
end
defp render_log(log) do
topics =
Enum.reject(
[log.first_topic, log.second_topic, log.third_topic, log.fourth_topic],
&is_nil/1
)
%{
"address" => to_string(log.address_hash),
"blockHash" => to_string(log.block_hash),
"blockNumber" => Integer.to_string(log.block_number, 16),
"data" => to_string(log.data),
"logIndex" => Integer.to_string(log.index, 16),
"removed" => log.block_consensus == false,
"topics" => topics,
"transactionHash" => to_string(log.transaction_hash),
"transactionIndex" => log.transaction_index,
"transactionLogIndex" => log.index,
"type" => "mined"
}
end
defp cast_block("0x" <> hexadecimal_digits = input) do
case Integer.parse(hexadecimal_digits, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, input <> " is not a valid block number"}
end
end
defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
defp cast_block(_), do: {:error, "invalid block number"}
defp address_or_topic_params(filter_options) do
address_param = Map.get(filter_options, "address")
topics_param = Map.get(filter_options, "topics")
with {:ok, address} <- validate_address(address_param),
{:ok, topics} <- validate_topics(topics_param) do
address_and_topics(address, topics)
end
end
defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
defp address_and_topics(nil, topics), do: {:ok, topics}
defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
defp validate_address(nil), do: {:ok, nil}
defp validate_address(address) do
case Address.cast(address) do
{:ok, address} -> {:ok, address}
:error -> {:error, "invalid address"}
end
end
defp validate_topics(nil), do: {:ok, nil}
defp validate_topics([]), do: []
defp validate_topics(topics) when is_list(topics) do
topics
|> Stream.with_index()
|> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
case cast_topics(topic) do
{:ok, data} ->
with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
{:ok, add_operator(with_filter, index)}
:error ->
{:error, "invalid topics"}
end
end)
end
defp add_operator(filters, 0), do: filters
defp add_operator(filters, index) do
Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
end
defp cast_topics(topics) when is_list(topics) do
case EctoType.cast({:array, Data}, topics) do
{:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
:error -> :error
end
end
defp cast_topics(topic) do
case Data.cast(topic) do
{:ok, data} -> {:ok, to_string(data)}
:error -> :error
end
end
defp responses(requests) do
Enum.map(requests, fn request ->
with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")},
{:request, {:ok, result}} <- {:request, do_eth_request(request)} do
format_success(result, id)
else
{:id, :error} -> format_error("id is a required field", 0)
{:request, {:error, message}} -> format_error(message, Map.get(request, "id"))
end
end)
end
defp logs_blocks_filter(filter_options) do
with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
{:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
{:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
{:ok, number, number}
else
{:filter, filters} ->
from_block = Map.get(filters, "fromBlock", "latest")
to_block = Map.get(filters, "toBlock", "latest")
max_block_number =
if from_block == "latest" || to_block == "latest" do
max_consensus_block_number()
end
pending_block_number =
if from_block == "pending" || to_block == "pending" do
max_non_consensus_block_number(max_block_number)
end
if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
{:error, :empty}
else
to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
end
{:block, _} ->
{:error, "Invalid Block Hash"}
{:block_hash, _} ->
{:error, "Invalid Block Hash"}
end
end
defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
actual_pending_block_number = pending_block_number || max_block_number
with {:ok, from} <-
to_block_number(from_block, max_block_number, actual_pending_block_number),
{:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
{:ok, from, to}
end
end
defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
defp to_block_number("earliest", _, _), do: {:ok, 0}
defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
defp to_block_number("pending", _, pending), do: {:ok, pending}
defp to_block_number("0x" <> number, _, _) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(number, _, _) when is_bitstring(number) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(_, _, _), do: {:error, "invalid block number"}
defp max_non_consensus_block_number(max) do
case Chain.max_non_consensus_block_number(max) do
{:ok, number} -> number
_ -> nil
end
end
defp max_consensus_block_number do
case Chain.max_consensus_block_number() do
{:ok, number} -> number
_ -> nil
end
end
defp format_success(result, id) do
%{result: result, id: id}
end
defp format_error(message, id) do
%{error: message, id: id}
end
defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do
{:error, "invalid rpc version"}
end
defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params})
when is_list(params) do
with {:ok, action} <- get_action(method),
{:correct_arity, true} <-
{:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
apply(__MODULE__, action, params)
else
{:correct_arity, _} ->
{:error, "Incorrect number of params."}
_ ->
{:error, "Action not found."}
end
end
defp do_eth_request(%{"params" => _params, "method" => _}) do
{:error, "Invalid params. Params must be a list."}
end
defp do_eth_request(_) do
{:error, "Method, params, and jsonrpc, are all required parameters."}
end
defp get_action(action) do
case Map.get(@methods, action) do
%{action: action} ->
{:ok, action}
_ ->
:error
end
end
defp block_param("latest"), do: {:ok, :latest}
defp block_param("earliest"), do: {:ok, :earliest}
defp block_param("pending"), do: {:ok, :pending}
defp block_param(string_integer) when is_bitstring(string_integer) do
case Integer.parse(string_integer) do
{integer, ""} -> {:ok, integer}
_ -> :error
end
end
defp block_param(nil), do: {:ok, :latest}
defp block_param(_), do: :error
end end

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash), {:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction, paging_options) logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs) {logs, next_page} = split_list_by_page(logs)
render(conn, :gettxinfo, %{ render(conn, :gettxinfo, %{

@ -7,8 +7,9 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
def create(conn, params) do def create(conn, params) do
if auth_token(conn) == actual_token() do if auth_token(conn) == actual_token() do
with {:ok, hash} <- validate_address_hash(params["address_hash"]), with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash), :ok <- Chain.check_address_exists(hash),
:ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do {:contract, :not_found} <-
{:contract, Chain.check_decompiled_contract_exists(params["address_hash"], params["decompiler_version"])} do
case Chain.create_decompiled_smart_contract(params) do case Chain.create_decompiled_smart_contract(params) do
{:ok, decompiled_smart_contract} -> {:ok, decompiled_smart_contract} ->
send_resp(conn, :created, Jason.encode!(decompiled_smart_contract)) send_resp(conn, :created, Jason.encode!(decompiled_smart_contract))
@ -29,7 +30,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
:not_found -> :not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists -> {:contract, :ok} ->
send_resp( send_resp(
conn, conn,
:unprocessable_entity, :unprocessable_entity,
@ -41,13 +42,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end end
end end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash} {:ok, hash} -> {:ok, hash}
@ -55,13 +49,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end end
end end
defp decompiled_contract_exists?(address_hash, decompiler_version) do
case Chain.decompiled_code(address_hash, decompiler_version) do
{:ok, _} -> :contract_exists
_ -> :ok
end
end
defp auth_token(conn) do defp auth_token(conn) do
case get_req_header(conn, "auth_token") do case get_req_header(conn, "auth_token") do
[token] -> token [token] -> token

@ -7,8 +7,8 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
def create(conn, params) do def create(conn, params) do
with {:ok, hash} <- validate_address_hash(params["address_hash"]), with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash), :ok <- Chain.check_address_exists(hash),
:ok <- verified_smart_contract_exists?(hash) do {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do
external_libraries = fetch_external_libraries(params) external_libraries = fetch_external_libraries(params)
case Publisher.publish(hash, params, external_libraries) do case Publisher.publish(hash, params, external_libraries) do
@ -31,7 +31,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
:not_found -> :not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists -> {:contract, :ok} ->
send_resp( send_resp(
conn, conn,
:unprocessable_entity, :unprocessable_entity,
@ -40,13 +40,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end end
end end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash} {:ok, hash} -> {:ok, hash}
@ -54,14 +47,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end end
end end
defp verified_smart_contract_exists?(address_hash) do
if Chain.address_hash_to_smart_contract(address_hash) do
:contract_exists
else
:ok
end
end
defp encode(data) do defp encode(data) do
Jason.encode!(data) Jason.encode!(data)
end end

@ -1,8 +1,8 @@
defmodule BlockScoutWeb.APIDocsController do defmodule BlockScoutWeb.APIDocsController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.EthController
alias BlockScoutWeb.Etherscan alias BlockScoutWeb.Etherscan
alias Explorer.EthRPC
def index(conn, _params) do def index(conn, _params) do
conn conn
@ -12,7 +12,7 @@ defmodule BlockScoutWeb.APIDocsController do
def eth_rpc(conn, _params) do def eth_rpc(conn, _params) do
conn conn
|> assign(:documentation, EthController.methods()) |> assign(:documentation, EthRPC.methods())
|> render("eth_rpc.html") |> render("eth_rpc.html")
end end
end end

@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
paging_options(params) paging_options(params)
) )
transactions_plus_one = Chain.block_to_transactions(block, full_options) transactions_plus_one = Chain.block_to_transactions(block.hash, full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one) {transactions, next_page} = split_list_by_page(transactions_plus_one)
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
:rewards => :optional :rewards => :optional
} }
) do ) do
block_transaction_count = Chain.block_to_transaction_count(block) block_transaction_count = Chain.block_to_transaction_count(block.hash)
render( render(
conn, conn,

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.ChainController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.ChainView alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Supply.RSK
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
@ -72,9 +72,15 @@ defmodule BlockScoutWeb.ChainController do
def chain_blocks(conn, _params) do def chain_blocks(conn, _params) do
if ajax?(conn) do if ajax?(conn) do
blocks = blocks =
[paging_options: %PagingOptions{page_size: 4}] [
paging_options: %PagingOptions{page_size: 4},
necessity_by_association: %{
[miner: :names] => :optional,
:transactions => :optional,
:rewards => :optional
}
]
|> Chain.list_blocks() |> Chain.list_blocks()
|> Repo.preload([[miner: :names], :transactions, :rewards])
|> Enum.map(fn block -> |> Enum.map(fn block ->
%{ %{
chain_block_html: chain_block_html:

@ -33,7 +33,7 @@ defmodule BlockScoutWeb.SmartContractController do
def show(conn, params) do def show(conn, params) do
with true <- ajax?(conn), with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
{:ok, _address} <- Chain.find_contract_address(address_hash), :ok <- Chain.check_contract_address_exists(address_hash),
outputs = outputs =
Reader.query_function( Reader.query_function(
address_hash, address_hash,
@ -51,7 +51,7 @@ defmodule BlockScoutWeb.SmartContractController do
:error -> :error ->
unprocessable_entity(conn) unprocessable_entity(conn)
{:error, :not_found} -> :not_found ->
not_found(conn) not_found(conn)
_ -> _ ->

@ -62,15 +62,15 @@ defmodule BlockScoutWeb.TransactionController do
def show(conn, %{"id" => id}) do def show(conn, %{"id" => id}) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id), with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id),
{:ok, %Chain.Transaction{} = transaction} <- Chain.hash_to_transaction(transaction_hash) do :ok <- Chain.check_transaction_exists(transaction_hash) do
if Chain.transaction_has_token_transfers?(transaction.hash) do if Chain.transaction_has_token_transfers?(transaction_hash) do
redirect(conn, to: transaction_token_transfer_path(conn, :index, id)) redirect(conn, to: transaction_token_transfer_path(conn, :index, id))
else else
redirect(conn, to: transaction_internal_transaction_path(conn, :index, id)) redirect(conn, to: transaction_internal_transaction_path(conn, :index, id))
end end
else else
:error -> conn |> put_status(422) |> render("invalid.html", transaction_hash: id) :error -> conn |> put_status(422) |> render("invalid.html", transaction_hash: id)
{:error, :not_found} -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id) :not_found -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id)
end end
end end
end end

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
{:ok, transaction} <- Chain.hash_to_transaction(hash) do :ok <- Chain.check_transaction_exists(hash) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
paging_options(params) paging_options(params)
) )
internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction, full_options) internal_transactions_plus_one = Chain.transaction_to_internal_transactions(hash, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one) {internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
@ -37,7 +37,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
transaction_internal_transaction_path( transaction_internal_transaction_path(
conn, conn,
:index, :index,
transaction, hash,
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -66,7 +66,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
|> put_view(TransactionView) |> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string) |> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} -> :not_found ->
conn conn
|> put_status(404) |> put_status(404)
|> put_view(TransactionView) |> put_view(TransactionView)

@ -11,7 +11,9 @@ defmodule BlockScoutWeb.TransactionLogController do
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string),
{:ok, transaction} <- {:ok, transaction} <-
Chain.hash_to_transaction(transaction_hash) do Chain.hash_to_transaction(transaction_hash,
necessity_by_association: %{[to_address: :smart_contract] => :optional}
) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -22,7 +24,7 @@ defmodule BlockScoutWeb.TransactionLogController do
paging_options(params) paging_options(params)
) )
logs_plus_one = Chain.transaction_to_logs(transaction, full_options) logs_plus_one = Chain.transaction_to_logs(transaction_hash, full_options)
{logs, next_page} = split_list_by_page(logs_plus_one) {logs, next_page} = split_list_by_page(logs_plus_one)

@ -19,7 +19,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional :token_transfers => :optional
} }
) do ) do
internal_transactions = Chain.transaction_to_internal_transactions(transaction) internal_transactions = Chain.transaction_to_internal_transactions(hash)
render( render(
conn, conn,

@ -10,8 +10,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
{:ok, transaction} <- :ok <- Chain.check_transaction_exists(hash) do
Chain.hash_to_transaction(hash) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -24,7 +23,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
paging_options(params) paging_options(params)
) )
token_transfers_plus_one = Chain.transaction_to_token_transfers(transaction, full_options) token_transfers_plus_one = Chain.transaction_to_token_transfers(hash, full_options)
{token_transfers, next_page} = split_list_by_page(token_transfers_plus_one) {token_transfers, next_page} = split_list_by_page(token_transfers_plus_one)
@ -34,7 +33,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
nil nil
next_page_params -> next_page_params ->
transaction_token_transfer_path(conn, :index, transaction, Map.delete(next_page_params, "type")) transaction_token_transfer_path(conn, :index, hash, Map.delete(next_page_params, "type"))
end end
items = items =
@ -62,7 +61,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
|> put_view(TransactionView) |> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string) |> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} -> :not_found ->
conn conn
|> put_status(404) |> put_status(404)
|> put_view(TransactionView) |> put_view(TransactionView)

@ -21,7 +21,7 @@
<td class="stakes-td color-lighten"> <td class="stakes-td color-lighten">
<!-- percentage of coins from total supply --> <!-- percentage of coins from total supply -->
<%= if @total_supply do %> <%= if @total_supply do %>
(<%= balance_percentage(@address, @total_supply) %>) <%= balance_percentage(@address, @total_supply) %>
<% end %> <% end %>
</td> </td>
<td class="stakes-td"> <td class="stakes-td">

@ -38,7 +38,7 @@
</tr> </tr>
</thead> </thead>
<tbody data-items data-selector="top-addresses-list"> <tbody data-items data-selector="top-addresses-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html" %>
</tbody> </tbody>
</table> </table>
</div> </div>

@ -40,7 +40,9 @@
</div> </div>
</div> </div>
<div data-selector="coin-balances-list" data-items></div> <div data-selector="coin-balances-list" data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -46,14 +46,18 @@
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.evm_version %></dd> <dd class="col-sm-8 col-md-10"><%= @address.smart_contract.evm_version %></dd>
</dl> </dl>
<% end %> <% end %>
<hr/>
<%= if @address.smart_contract.constructor_arguments do %> <%= if @address.smart_contract.constructor_arguments do %>
<dl class="row"> <section>
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Constructor arguments" %></dt> <div class="d-flex justify-content-between align-items-baseline">
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.constructor_arguments %></dd> <h3><%= gettext "Constructor Arguments" %></h3>
</dl>
<% end %>
</div> </div>
<hr/> <div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= raw(format_constructor_arguments(@address.smart_contract)) %></code>
</pre>
</div>
</section>
<% end %>
<section> <section>
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract source code" %></h3> <h3><%= gettext "Contract source code" %></h3>
@ -116,7 +120,7 @@
<h3><%= gettext "External libraries" %></h3> <h3><%= gettext "External libraries" %></h3>
</div> </div>
<div class="tile tile-muted mb-4"> <div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= format_external_libraries(@address.smart_contract.external_libraries) %></code> <pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= raw(format_external_libraries(@address.smart_contract.external_libraries)) %></code>
</pre> </pre>
</div> </div>
</section> </section>

@ -66,7 +66,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -27,7 +27,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -19,7 +19,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div class="transaction-bottom-panel"> <div class="transaction-bottom-panel">
<div csv-download class="download-all-transactions"> <div csv-download class="download-all-transactions">

@ -21,7 +21,9 @@
</span> </span>
</button> </button>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -65,7 +65,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div class="transaction-bottom-panel"> <div class="transaction-bottom-panel">
<div class="download-all-transactions"> <div class="download-all-transactions">

@ -22,7 +22,9 @@
<%= gettext "Something went wrong, click to reload." %> <%= gettext "Something went wrong, click to reload." %>
</span> </span>
</button> </button>
<div data-items data-selector="validations-list"></div> <div data-items data-selector="validations-list">
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -11,7 +11,9 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div data-empty-response-message style="display: none;"> <div data-empty-response-message style="display: none;">
<span><%= gettext "There are no blocks." %></span> <span><%= gettext "There are no blocks." %></span>
</div> </div>

@ -29,7 +29,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -1,5 +1,172 @@
<div class="col-lg-3 fade-up-blocks-chain" data-selector="chain-block" data-block-number="<%= @block.number %>"> <div class="col-lg-3 fade-up-blocks-chain" data-selector="chain-block" data-block-number="<%= @block.number %>">
<div class="tile tile-type-block n-p d-flex flex-column"> <div class="tile tile-type-block n-p d-flex flex-column">
<div class="tile-type-block-animation">
<div class="tile-type-line-up"></div>
<span class="cube-animation-title">Block Validated, processing...</span>
<div class="cube-animation-wrapper">
<div class="cube-animation-center">
<div class="cube-animation-row">
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
</div>
<div class="cube-animation-row">
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
</div>
<div class="cube-animation-row">
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
<div class="cube-animation-column">
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
<svg class="cube" viewBox="0 0 86.6 100" x="0px" y="0px">
<polygon class="side" points="43.3,0 0,25 0,75 43.3,100 86.6,75 86.6,25 "></polygon>
<polygon class="side" points="86.6,25 43.3,50 43.3,100 86.6,75 "></polygon>
<polygon class="side" points="0,25 43.3,0 86.6,25 43.3,50 "></polygon>
</svg>
</div>
</div>
</div>
</div>
</div>
<%= link( <%= link(
@block, @block,
class: "tile-title", class: "tile-title",

@ -5,7 +5,7 @@
<div class="dashboard-banner-network-graph"> <div class="dashboard-banner-network-graph">
<!-- Graph --> <!-- Graph -->
<div class="dashboard-banner-chart"> <div class="dashboard-banner-chart">
<div data-chart-loading-message class="tile tile-muted text-center mt-5"> <div hidden data-chart-loading-message class="tile tile-muted text-center mt-5">
<span class="loading-spinner-small mr-2"> <span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span> <span class="loading-spinner-block-2"></span>
@ -91,7 +91,7 @@
<%= gettext "Something went wrong, click to reload." %> <%= gettext "Something went wrong, click to reload." %>
</span> </span>
</button> </button>
<div data-selector="loading-message" class="tile tile-muted text-center mt-3 w-100"> <div hidden data-selector="loading-message" class="tile tile-muted text-center mt-3 w-100" >
<span class="loading-spinner-small mr-2"> <span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span> <span class="loading-spinner-block-2"></span>
@ -117,13 +117,7 @@
<%= gettext "Something went wrong, click to retry." %> <%= gettext "Something went wrong, click to retry." %>
</span> </span>
</button> </button>
<div data-selector="loading-message" class="tile tile-muted text-center mt-3"> <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
<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>
</span> </span>
</div> </div>
</div> </div>

@ -0,0 +1,85 @@
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
</tr>

@ -0,0 +1,72 @@
<div data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="tile-status-label ml-2 ml-md-0">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<span class="tile-loader tile-address-loader"></span>
<span class="tile-loader tile-address-loader"></span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="mr-2 mr-md-0 order-2">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
</div>
</div>
<div data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="tile-status-label ml-2 ml-md-0">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<span class="tile-loader tile-address-loader"></span>
<span class="tile-loader tile-address-loader"></span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="mr-2 mr-md-0 order-2">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
</div>
</div>
<div data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="tile-status-label ml-2 ml-md-0">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<span class="tile-loader tile-address-loader"></span>
<span class="tile-loader tile-address-loader"></span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="mr-2 mr-md-0 order-2">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
</div>
</div>

@ -23,13 +23,8 @@
<%= gettext "There are no pending transactions." %> <%= gettext "There are no pending transactions." %>
</span> </span>
</div> </div>
<div data-items data-selector="transactions-pending-list"></div> <div data-items data-selector="transactions-pending-list">
<div data-loading-message class="tile tile-muted text-center mt-3" style="display: none;"> <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
<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>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -28,7 +28,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -27,7 +27,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -26,7 +26,9 @@
</span> </span>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -28,7 +28,9 @@
</div> </div>
</div> </div>
<div data-selector="transactions-list" data-items></div> <div data-selector="transactions-list" data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -16,7 +16,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div> </div>

@ -19,7 +19,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -18,7 +18,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>

@ -1,6 +1,7 @@
defmodule BlockScoutWeb.AddressContractView do defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain.{Address, Data, InternalTransaction} alias Explorer.Chain.{Address, Data, InternalTransaction}
def render("scripts.html", %{conn: conn}) do def render("scripts.html", %{conn: conn}) do
@ -21,9 +22,44 @@ defmodule BlockScoutWeb.AddressContractView do
def format_optimization_text(true), do: gettext("true") def format_optimization_text(true), do: gettext("true")
def format_optimization_text(false), do: gettext("false") def format_optimization_text(false), do: gettext("false")
def format_constructor_arguments(contract) do
constructor_abi = Enum.find(contract.abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end)
input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1)
{_, result} =
contract.constructor_arguments
|> decode_data(input_types)
|> Enum.zip(constructor_abi["inputs"])
|> Enum.reduce({0, "#{contract.constructor_arguments}\n\n"}, fn {val, %{"type" => type}}, {count, acc} ->
formatted_val =
if is_binary(val) do
Base.encode16(val, case: :lower)
else
val
end
{count + 1, "#{acc}Arg [#{count}] (<b>#{type}</b>) : #{formatted_val}\n"}
end)
result
rescue
_ -> contract.constructor_arguments
end
defp decode_data("0x" <> encoded_data, types) do
decode_data(encoded_data, types)
end
defp decode_data(encoded_data, types) do
encoded_data
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)
end
def format_external_libraries(libraries) do def format_external_libraries(libraries) do
Enum.reduce(libraries, "", fn %{name: name, address_hash: address_hash}, acc -> Enum.reduce(libraries, "", fn %{name: name, address_hash: address_hash}, acc ->
acc <> name <> " : " <> address_hash <> "\n" "#{acc}<span class=\"hljs-title\">#{name}</span> : #{address_hash} \n"
end) end)
end end

@ -34,7 +34,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:21 #: lib/block_scout_web/templates/block/overview.html.eex:21
#: lib/block_scout_web/templates/chain/_block.html.eex:11 #: lib/block_scout_web/templates/chain/_block.html.eex:178
msgid "%{count} Transactions" msgid "%{count} Transactions"
msgstr "" msgstr ""
@ -258,7 +258,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contract ABI" msgid "Contract ABI"
msgstr "" msgstr ""
@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:59 #: lib/block_scout_web/templates/address_contract/index.html.eex:63
msgid "Contract source code" msgid "Contract source code"
msgstr "" msgstr ""
@ -522,7 +522,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:38 #: lib/block_scout_web/templates/block/_tile.html.eex:38
#: lib/block_scout_web/templates/block/overview.html.eex:121 #: lib/block_scout_web/templates/block/overview.html.eex:121
#: lib/block_scout_web/templates/chain/_block.html.eex:15 #: lib/block_scout_web/templates/chain/_block.html.eex:182
msgid "Miner" msgid "Miner"
msgstr "" msgstr ""
@ -1001,7 +1001,7 @@ msgid "at"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:22 #: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false" msgid "false"
msgstr "" msgstr ""
@ -1019,7 +1019,7 @@ msgid "string"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:21 #: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true" msgid "true"
msgstr "" msgstr ""
@ -1040,7 +1040,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:47 #: lib/block_scout_web/templates/block/_tile.html.eex:47
#: lib/block_scout_web/templates/chain/_block.html.eex:23 #: lib/block_scout_web/templates/chain/_block.html.eex:190
#: lib/block_scout_web/views/internal_transaction_view.ex:27 #: lib/block_scout_web/views/internal_transaction_view.ex:27
msgid "Reward" msgid "Reward"
msgstr "" msgstr ""
@ -1058,7 +1058,6 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/chain/show.html.eex:99 #: lib/block_scout_web/templates/chain/show.html.eex:99
#: lib/block_scout_web/templates/chain/show.html.eex:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1078,11 +1077,6 @@ msgstr ""
msgid "GraphQL" msgid "GraphQL"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
msgid "Loading"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 #: lib/block_scout_web/templates/layout/_topnav.html.eex:76
msgid "RPC" msgid "RPC"
@ -1277,7 +1271,7 @@ msgid "There are no pending transactions."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/index.html.eex:16 #: lib/block_scout_web/templates/block/index.html.eex:18
msgid "There are no blocks." msgid "There are no blocks."
msgstr "" msgstr ""
@ -1407,17 +1401,17 @@ msgid "Support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:73 #: lib/block_scout_web/templates/address_contract/index.html.eex:77
msgid "Copy ABI" msgid "Copy ABI"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:89 #: lib/block_scout_web/templates/address_contract/index.html.eex:93
msgid "Copy Contract Creation Code" msgid "Copy Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:61 #: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Copy Source Code" msgid "Copy Source Code"
msgstr "" msgstr ""
@ -1587,27 +1581,27 @@ msgid "Block Details"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:101 #: lib/block_scout_web/templates/address_contract/index.html.eex:105
msgid "Contract Byte Code" msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:87 #: lib/block_scout_web/templates/address_contract/index.html.eex:91
msgid "Contract Creation Code" msgid "Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:93 #: lib/block_scout_web/templates/address_contract/index.html.eex:97
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:103 #: lib/block_scout_web/templates/address_contract/index.html.eex:107
msgid "Copy Contract Byte Code" msgid "Copy Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:94 #: lib/block_scout_web/templates/address_contract/index.html.eex:98
msgid "Displaying the init data provided of the creating transaction." msgid "Displaying the init data provided of the creating transaction."
msgstr "" msgstr ""
@ -1745,8 +1739,8 @@ msgid "here."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26 #: lib/block_scout_web/templates/address_token/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72 #: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV" msgid "CSV"
msgstr "" msgstr ""
@ -1755,11 +1749,6 @@ msgstr ""
msgid "Change Network" msgid "Change Network"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:51
msgid "Constructor arguments"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44 #: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 " msgid "ERC-20 "
@ -1771,7 +1760,7 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:116 #: lib/block_scout_web/templates/address_contract/index.html.eex:120
msgid "External libraries" msgid "External libraries"
msgstr "" msgstr ""
@ -1804,3 +1793,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
msgid "Connection Lost" msgid "Connection Lost"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:53
msgid "Constructor Arguments"
msgstr ""

@ -34,7 +34,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:21 #: lib/block_scout_web/templates/block/overview.html.eex:21
#: lib/block_scout_web/templates/chain/_block.html.eex:11 #: lib/block_scout_web/templates/chain/_block.html.eex:178
msgid "%{count} Transactions" msgid "%{count} Transactions"
msgstr "" msgstr ""
@ -258,7 +258,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contract ABI" msgid "Contract ABI"
msgstr "" msgstr ""
@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:59 #: lib/block_scout_web/templates/address_contract/index.html.eex:63
msgid "Contract source code" msgid "Contract source code"
msgstr "" msgstr ""
@ -522,7 +522,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:38 #: lib/block_scout_web/templates/block/_tile.html.eex:38
#: lib/block_scout_web/templates/block/overview.html.eex:121 #: lib/block_scout_web/templates/block/overview.html.eex:121
#: lib/block_scout_web/templates/chain/_block.html.eex:15 #: lib/block_scout_web/templates/chain/_block.html.eex:182
msgid "Miner" msgid "Miner"
msgstr "" msgstr ""
@ -1001,7 +1001,7 @@ msgid "at"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:22 #: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false" msgid "false"
msgstr "" msgstr ""
@ -1019,7 +1019,7 @@ msgid "string"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:21 #: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true" msgid "true"
msgstr "" msgstr ""
@ -1040,7 +1040,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:47 #: lib/block_scout_web/templates/block/_tile.html.eex:47
#: lib/block_scout_web/templates/chain/_block.html.eex:23 #: lib/block_scout_web/templates/chain/_block.html.eex:190
#: lib/block_scout_web/views/internal_transaction_view.ex:27 #: lib/block_scout_web/views/internal_transaction_view.ex:27
msgid "Reward" msgid "Reward"
msgstr "" msgstr ""
@ -1058,7 +1058,6 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/chain/show.html.eex:99 #: lib/block_scout_web/templates/chain/show.html.eex:99
#: lib/block_scout_web/templates/chain/show.html.eex:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1078,11 +1077,6 @@ msgstr ""
msgid "GraphQL" msgid "GraphQL"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
msgid "Loading"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 #: lib/block_scout_web/templates/layout/_topnav.html.eex:76
msgid "RPC" msgid "RPC"
@ -1277,7 +1271,7 @@ msgid "There are no pending transactions."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/index.html.eex:16 #: lib/block_scout_web/templates/block/index.html.eex:18
msgid "There are no blocks." msgid "There are no blocks."
msgstr "" msgstr ""
@ -1407,17 +1401,17 @@ msgid "Support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:73 #: lib/block_scout_web/templates/address_contract/index.html.eex:77
msgid "Copy ABI" msgid "Copy ABI"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:89 #: lib/block_scout_web/templates/address_contract/index.html.eex:93
msgid "Copy Contract Creation Code" msgid "Copy Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:61 #: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Copy Source Code" msgid "Copy Source Code"
msgstr "" msgstr ""
@ -1587,27 +1581,27 @@ msgid "Block Details"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:101 #: lib/block_scout_web/templates/address_contract/index.html.eex:105
msgid "Contract Byte Code" msgid "Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:87 #: lib/block_scout_web/templates/address_contract/index.html.eex:91
msgid "Contract Creation Code" msgid "Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:93 #: lib/block_scout_web/templates/address_contract/index.html.eex:97
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:103 #: lib/block_scout_web/templates/address_contract/index.html.eex:107
msgid "Copy Contract Byte Code" msgid "Copy Contract Byte Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:94 #: lib/block_scout_web/templates/address_contract/index.html.eex:98
msgid "Displaying the init data provided of the creating transaction." msgid "Displaying the init data provided of the creating transaction."
msgstr "" msgstr ""
@ -1745,8 +1739,8 @@ msgid "here."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26 #: lib/block_scout_web/templates/address_token/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72 #: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV" msgid "CSV"
msgstr "" msgstr ""
@ -1755,11 +1749,6 @@ msgstr ""
msgid "Change Network" msgid "Change Network"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:51
msgid "Constructor arguments"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44 #: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 " msgid "ERC-20 "
@ -1771,7 +1760,7 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:116 #: lib/block_scout_web/templates/address_contract/index.html.eex:120
msgid "External libraries" msgid "External libraries"
msgstr "" msgstr ""
@ -1804,3 +1793,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
msgid "Connection Lost" msgid "Connection Lost"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:53
msgid "Constructor Arguments"
msgstr ""

@ -125,6 +125,57 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data")) assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data"))
end end
test "paginates logs", %{conn: conn, api_params: api_params} do
contract_address = insert(:contract_address)
transaction =
:transaction
|> insert(to_address: contract_address)
|> with_block()
inserted_records =
insert_list(2000, :log, address: contract_address, transaction: transaction, first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert Enum.count(response["result"]) == 1000
{last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16)
next_page_params = %{
"blockNumber" => Integer.to_string(transaction.block_number, 16),
"transactionIndex" => transaction.index,
"logIndex" => Integer.to_string(last_log_index, 16)
}
new_params =
params(api_params, [
%{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]}
])
assert new_response =
conn
|> post("/api/eth_rpc", new_params)
|> json_response(200)
assert Enum.count(response["result"]) == 1000
all_found_logs = response["result"] ++ new_response["result"]
assert Enum.all?(inserted_records, fn record ->
Enum.any?(all_found_logs, fn found_log ->
{index, ""} = Integer.parse(found_log["logIndex"], 16)
record.index == index
end)
end)
end
test "with a matching address and multiple topic matches in different positions", %{ test "with a matching address and multiple topic matches in different positions", %{
conn: conn, conn: conn,
api_params: api_params api_params: api_params

@ -17,7 +17,15 @@ config :explorer,
if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true), if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true),
healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5) healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5)
config :explorer, Explorer.Counters.AverageBlockTime, enabled: true average_block_period =
case Integer.parse(System.get_env("AVERAGE_BLOCK_CACHE_PERIOD", "")) do
{secs, ""} -> :timer.seconds(secs)
_ -> :timer.minutes(30)
end
config :explorer, Explorer.Counters.AverageBlockTime,
enabled: true,
period: average_block_period
config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true
@ -106,6 +114,14 @@ config :spandex_ecto, SpandexEcto.EctoLogger,
tracer: Explorer.Tracer, tracer: Explorer.Tracer,
otp_app: :explorer otp_app: :explorer
market_history_cache_period =
case Integer.parse(System.get_env("MARKET_HISTORY_CACHE_PERIOD", "")) do
{secs, ""} -> :timer.seconds(secs)
_ -> :timer.hours(6)
end
config :explorer, Explorer.Market.MarketHistoryCache, period: market_history_cache_period
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

@ -364,8 +364,8 @@ defmodule Explorer.Chain do
Uncles are not currently accounted for. Uncles are not currently accounted for.
""" """
@spec block_reward(Block.t()) :: Wei.t() @spec block_reward(Block.block_number()) :: Wei.t()
def block_reward(%Block{number: block_number}) do def block_reward(block_number) do
query = query =
from( from(
block in Block, block in Block,
@ -415,8 +415,8 @@ defmodule Explorer.Chain do
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than `:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than
the `index` that are passed. the `index` that are passed.
""" """
@spec block_to_transactions(Block.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()] @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
def block_to_transactions(%Block{hash: block_hash}, options \\ []) when is_list(options) do def block_to_transactions(block_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options options
@ -432,8 +432,8 @@ defmodule Explorer.Chain do
@doc """ @doc """
Counts the number of `t:Explorer.Chain.Transaction.t/0` in the `block`. Counts the number of `t:Explorer.Chain.Transaction.t/0` in the `block`.
""" """
@spec block_to_transaction_count(Block.t()) :: non_neg_integer() @spec block_to_transaction_count(Hash.Full.t()) :: non_neg_integer()
def block_to_transaction_count(%Block{hash: block_hash}) do def block_to_transaction_count(block_hash) do
query = query =
from( from(
transaction in Transaction, transaction in Transaction,
@ -843,7 +843,7 @@ defmodule Explorer.Chain do
Returns `{:error, :not_found}` if there is no address by that hash present. Returns `{:error, :not_found}` if there is no address by that hash present.
Returns `{:error, :no_balance}` if there is no balance for that address at that block. Returns `{:error, :no_balance}` if there is no balance for that address at that block.
""" """
@spec get_balance_as_of_block(Hash.Address.t(), integer | :earliest | :latest | :pending) :: @spec get_balance_as_of_block(Hash.Address.t(), Block.block_number() | :earliest | :latest | :pending) ::
{:ok, Wei.t()} | {:error, :no_balance} | {:error, :not_found} {:ok, Wei.t()} | {:error, :no_balance} | {:error, :not_found}
def get_balance_as_of_block(address, block) when is_integer(block) do def get_balance_as_of_block(address, block) when is_integer(block) do
coin_balance_query = coin_balance_query =
@ -930,33 +930,44 @@ defmodule Explorer.Chain do
Repo.all(query) Repo.all(query)
end end
@spec find_contract_address(Hash.t()) :: {:ok, Address.t()} | {:error, :not_found} @doc """
def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do Finds an `t:Explorer.Chain.Address.t/0` that has the provided `t:Explorer.Chain.Address.t/0` `hash` and a contract.
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
"""
@spec find_contract_address(Hash.Address.t(), [necessity_by_association_option], boolean()) ::
{:ok, Address.t()} | {:error, :not_found}
def find_contract_address(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
options \\ [],
query_decompiled_code_flag \\ false
) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
query = query =
from( from(
address in Address, address in Address,
preload: [
:contracts_creation_internal_transaction,
:names,
:smart_contract,
:token,
:contracts_creation_transaction
],
where: address.hash == ^hash and not is_nil(address.contract_code) where: address.hash == ^hash and not is_nil(address.contract_code)
) )
query_with_decompiled_flag = with_decompiled_code_flag(query, hash) query
|> join_associations(necessity_by_association)
address = Repo.one(query_with_decompiled_flag) |> with_decompiled_code_flag(hash, query_decompiled_code_flag)
|> Repo.one()
if address do |> case do
{:ok, address} nil -> {:error, :not_found}
else address -> {:ok, address}
{:error, :not_found}
end end
end end
@spec find_decompiled_contract_address(Hash.t()) :: {:ok, Address.t()} | {:error, :not_found} @spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query = query =
from( from(
@ -2209,14 +2220,10 @@ defmodule Explorer.Chain do
""" """
@spec transaction_to_internal_transactions(Transaction.t(), [paging_options | necessity_by_association_option]) :: [ @spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t() InternalTransaction.t()
] ]
def transaction_to_internal_transactions( def transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash},
options \\ []
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -2244,12 +2251,8 @@ defmodule Explorer.Chain do
the `index` that are passed. the `index` that are passed.
""" """
@spec transaction_to_logs(Transaction.t(), [paging_options | necessity_by_association_option]) :: [Log.t()] @spec transaction_to_logs(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
def transaction_to_logs( def transaction_to_logs(transaction_hash, options \\ []) when is_list(options) do
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
options \\ []
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -2276,14 +2279,10 @@ defmodule Explorer.Chain do
the `index` that are passed. the `index` that are passed.
""" """
@spec transaction_to_token_transfers(Transaction.t(), [paging_options | necessity_by_association_option]) :: [ @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
TokenTransfer.t() TokenTransfer.t()
] ]
def transaction_to_token_transfers( def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
options \\ []
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -2510,16 +2509,16 @@ defmodule Explorer.Chain do
|> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
end end
@spec address_hash_to_address_with_source_code(%Explorer.Chain.Hash{}) :: %Explorer.Chain.Address{} | nil @spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(%Explorer.Chain.Hash{} = address_hash) do def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do case Repo.get(Address, address_hash) do
nil -> nil nil -> nil
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts]) address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
end end
end end
@spec address_hash_to_smart_contract(%Explorer.Chain.Hash{}) :: %Explorer.Chain.SmartContract{} | nil @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil
def address_hash_to_smart_contract(%Explorer.Chain.Hash{} = address_hash) do def address_hash_to_smart_contract(address_hash) do
query = query =
from( from(
smart_contract in SmartContract, smart_contract in SmartContract,
@ -3276,8 +3275,6 @@ defmodule Explorer.Chain do
defp staking_pool_filter(query, _), do: query defp staking_pool_filter(query, _), do: query
defp with_decompiled_code_flag(query, hash, use_option \\ true)
defp with_decompiled_code_flag(query, _hash, false), do: query defp with_decompiled_code_flag(query, _hash, false), do: query
defp with_decompiled_code_flag(query, hash, true) do defp with_decompiled_code_flag(query, hash, true) do
@ -3300,4 +3297,203 @@ defmodule Explorer.Chain do
|> Base.decode16!(case: :mixed) |> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types) |> TypeDecoder.decode_raw(types)
end end
@doc """
Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists.
Returns `:ok` if found
iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address(
...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"}
...> )
iex> Explorer.Chain.check_address_exists(hash)
:ok
Returns `:not_found` if not found
iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
iex> Explorer.Chain.check_address_exists(hash)
:not_found
"""
@spec check_address_exists(Hash.Address.t()) :: :ok | :not_found
def check_address_exists(address_hash) do
address_hash
|> address_exists?()
|> boolean_to_check_result()
end
@doc """
Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists.
Returns `true` if found
iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address(
...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"}
...> )
iex> Explorer.Chain.address_exists?(hash)
true
Returns `false` if not found
iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
iex> Explorer.Chain.address_exists?(hash)
false
"""
@spec address_exists?(Hash.Address.t()) :: boolean()
def address_exists?(address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash
)
Repo.exists?(query)
end
@doc """
Checks if it exists an `t:Explorer.Chain.Address.t/0` that has the provided
`t:Explorer.Chain.Address.t/0` `hash` and a contract.
Returns `:ok` if found and `:not_found` otherwise.
"""
@spec check_contract_address_exists(Hash.Address.t()) :: :ok | :not_found
def check_contract_address_exists(address_hash) do
address_hash
|> contract_address_exists?()
|> boolean_to_check_result()
end
@doc """
Checks if it exists an `t:Explorer.Chain.Address.t/0` that has the provided
`t:Explorer.Chain.Address.t/0` `hash` and a contract.
Returns `true` if found and `false` otherwise.
"""
@spec contract_address_exists?(Hash.Address.t()) :: boolean()
def contract_address_exists?(address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash and not is_nil(address.contract_code)
)
Repo.exists?(query)
end
@doc """
Checks if it exists a `t:Explorer.Chain.DecompiledSmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash` and with the provided version.
Returns `:ok` if found and `:not_found` otherwise.
"""
@spec check_decompiled_contract_exists(Hash.Address.t(), String.t()) :: :ok | :not_found
def check_decompiled_contract_exists(address_hash, version) do
address_hash
|> decompiled_contract_exists?(version)
|> boolean_to_check_result()
end
@doc """
Checks if it exists a `t:Explorer.Chain.DecompiledSmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash` and with the provided version.
Returns `true` if found and `false` otherwise.
"""
@spec decompiled_contract_exists?(Hash.Address.t(), String.t()) :: boolean()
def decompiled_contract_exists?(address_hash, version) do
query =
from(contract in DecompiledSmartContract,
where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version
)
Repo.exists?(query)
end
@doc """
Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash`.
Returns `:ok` if found and `:not_found` otherwise.
"""
@spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found
def check_verified_smart_contract_exists(address_hash) do
address_hash
|> verified_smart_contract_exists?()
|> boolean_to_check_result()
end
@doc """
Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash`.
Returns `true` if found and `false` otherwise.
"""
@spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean()
def verified_smart_contract_exists?(address_hash) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
Repo.exists?(query)
end
@doc """
Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
Returns `:ok` if found
iex> %Transaction{hash: hash} = insert(:transaction)
iex> Explorer.Chain.check_transaction_exists(hash)
:ok
Returns `:not_found` if not found
iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash(
...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b"
...> )
iex> Explorer.Chain.check_transaction_exists(hash)
:not_found
"""
@spec check_transaction_exists(Hash.Full.t()) :: :ok | :not_found
def check_transaction_exists(hash) do
hash
|> transaction_exists?()
|> boolean_to_check_result()
end
@doc """
Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
Returns `true` if found
iex> %Transaction{hash: hash} = insert(:transaction)
iex> Explorer.Chain.transaction_exists?(hash)
true
Returns `false` if not found
iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash(
...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b"
...> )
iex> Explorer.Chain.transaction_exists?(hash)
false
"""
@spec transaction_exists?(Hash.Full.t()) :: boolean()
def transaction_exists?(hash) do
query =
from(
transaction in Transaction,
where: transaction.hash == ^hash
)
Repo.exists?(query)
end
defp boolean_to_check_result(true), do: :ok
defp boolean_to_check_result(false), do: :not_found
end end

@ -416,7 +416,8 @@ defmodule Explorer.Chain.Transaction do
candidates_query = candidates_query =
from( from(
contract_method in ContractMethod, contract_method in ContractMethod,
where: contract_method.identifier == ^method_id where: contract_method.identifier == ^method_id,
limit: 1
) )
candidates = candidates =

@ -11,7 +11,7 @@ defmodule Explorer.Counters.AverageBlockTime do
alias Explorer.Repo alias Explorer.Repo
alias Timex.Duration alias Timex.Duration
@refresh_period 30 * 60 * 1_000 @refresh_period Application.get_env(:explorer, __MODULE__)[:period]
@doc """ @doc """
Starts a process to periodically update the counter of the token holders. Starts a process to periodically update the counter of the token holders.

@ -0,0 +1,371 @@
defmodule Explorer.EthRPC do
@moduledoc """
Ethreum JSON RPC methods logic implementation.
"""
alias Ecto.Type, as: EctoType
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
alias Explorer.Etherscan.Logs
@methods %{
"eth_getBalance" => %{
action: :eth_get_balance,
notes: """
the `earliest` parameter will not work as expected currently, because genesis block balances
are not currently imported
""",
example: """
{"id": 0, "jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0x0000000000000000000000000000000000000007", "2"]}
"""
},
"eth_getLogs" => %{
action: :eth_get_logs,
notes: """
Will never return more than 1000 log entries.\n
For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination.
""",
example: """
{"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs",
"params": [
{"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f",
"paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53},
"fromBlock": "earliest",
"toBlock": "latest",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}
"""
}
}
@index_to_word %{
0 => "first",
1 => "second",
2 => "third",
3 => "fourth"
}
def responses(requests) do
Enum.map(requests, fn request ->
with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")},
{:request, {:ok, result}} <- {:request, do_eth_request(request)} do
format_success(result, id)
else
{:id, :error} -> format_error("id is a required field", 0)
{:request, {:error, message}} -> format_error(message, Map.get(request, "id"))
end
end)
end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
end
end
def eth_get_logs(filter_options) do
with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
{:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
{:ok, from_block} <- cast_block(from_block_param),
{:ok, to_block} <- cast_block(to_block_param),
{:ok, paging_options} <- paging_options(filter_options) do
filter =
address_or_topic_params
|> Map.put(:from_block, from_block)
|> Map.put(:to_block, to_block)
|> Map.put(:allow_non_consensus, true)
logs =
filter
|> Logs.list_logs(paging_options)
|> Enum.map(&render_log/1)
{:ok, logs}
else
{:error, message} when is_bitstring(message) ->
{:error, message}
{:error, :empty} ->
{:ok, []}
_ ->
{:error, "Something went wrong."}
end
end
defp render_log(log) do
topics =
Enum.reject(
[log.first_topic, log.second_topic, log.third_topic, log.fourth_topic],
&is_nil/1
)
%{
"address" => to_string(log.address_hash),
"blockHash" => to_string(log.block_hash),
"blockNumber" => Integer.to_string(log.block_number, 16),
"data" => to_string(log.data),
"logIndex" => Integer.to_string(log.index, 16),
"removed" => log.block_consensus == false,
"topics" => topics,
"transactionHash" => to_string(log.transaction_hash),
"transactionIndex" => log.transaction_index,
"transactionLogIndex" => log.index,
"type" => "mined"
}
end
defp cast_block("0x" <> hexadecimal_digits = input) do
case Integer.parse(hexadecimal_digits, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, input <> " is not a valid block number"}
end
end
defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
defp cast_block(_), do: {:error, "invalid block number"}
defp address_or_topic_params(filter_options) do
address_param = Map.get(filter_options, "address")
topics_param = Map.get(filter_options, "topics")
with {:ok, address} <- validate_address(address_param),
{:ok, topics} <- validate_topics(topics_param) do
address_and_topics(address, topics)
end
end
defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
defp address_and_topics(nil, topics), do: {:ok, topics}
defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
defp validate_address(nil), do: {:ok, nil}
defp validate_address(address) do
case Address.cast(address) do
{:ok, address} -> {:ok, address}
:error -> {:error, "invalid address"}
end
end
defp validate_topics(nil), do: {:ok, nil}
defp validate_topics([]), do: []
defp validate_topics(topics) when is_list(topics) do
topics
|> Stream.with_index()
|> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
case cast_topics(topic) do
{:ok, data} ->
with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
{:ok, add_operator(with_filter, index)}
:error ->
{:error, "invalid topics"}
end
end)
end
defp add_operator(filters, 0), do: filters
defp add_operator(filters, index) do
Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
end
defp cast_topics(topics) when is_list(topics) do
case EctoType.cast({:array, Data}, topics) do
{:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
:error -> :error
end
end
defp cast_topics(topic) do
case Data.cast(topic) do
{:ok, data} -> {:ok, to_string(data)}
:error -> :error
end
end
defp logs_blocks_filter(filter_options) do
with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
{:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
{:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
{:ok, number, number}
else
{:filter, filters} ->
from_block = Map.get(filters, "fromBlock", "latest")
to_block = Map.get(filters, "toBlock", "latest")
max_block_number =
if from_block == "latest" || to_block == "latest" do
max_consensus_block_number()
end
pending_block_number =
if from_block == "pending" || to_block == "pending" do
max_non_consensus_block_number(max_block_number)
end
if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
{:error, :empty}
else
to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
end
{:block, _} ->
{:error, "Invalid Block Hash"}
{:block_hash, _} ->
{:error, "Invalid Block Hash"}
end
end
defp paging_options(%{
"paging_options" => %{
"logIndex" => log_index,
"transactionIndex" => transaction_index,
"blockNumber" => block_number
}
})
when is_integer(transaction_index) do
with {:ok, parsed_block_number} <- to_number(block_number, "invalid block number"),
{:ok, parsed_log_index} <- to_number(log_index, "invalid log index") do
{:ok,
%{
log_index: parsed_log_index,
transaction_index: transaction_index,
block_number: parsed_block_number
}}
end
end
defp paging_options(_), do: {:ok, nil}
defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
actual_pending_block_number = pending_block_number || max_block_number
with {:ok, from} <-
to_block_number(from_block, max_block_number, actual_pending_block_number),
{:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
{:ok, from, to}
end
end
defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
defp to_block_number("earliest", _, _), do: {:ok, 0}
defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
defp to_block_number("pending", _, pending), do: {:ok, pending}
defp to_block_number("0x" <> number, _, _) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(number, _, _) when is_bitstring(number) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(_, _, _), do: {:error, "invalid block number"}
defp to_number(number, error_message) when is_bitstring(number) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, error_message}
end
end
defp to_number(_, error_message), do: {:error, error_message}
defp max_non_consensus_block_number(max) do
case Chain.max_non_consensus_block_number(max) do
{:ok, number} -> number
_ -> nil
end
end
defp max_consensus_block_number do
case Chain.max_consensus_block_number() do
{:ok, number} -> number
_ -> nil
end
end
defp format_success(result, id) do
%{result: result, id: id}
end
defp format_error(message, id) do
%{error: message, id: id}
end
defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do
{:error, "invalid rpc version"}
end
defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params})
when is_list(params) do
with {:ok, action} <- get_action(method),
{:correct_arity, true} <-
{:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
apply(__MODULE__, action, params)
else
{:correct_arity, _} ->
{:error, "Incorrect number of params."}
_ ->
{:error, "Action not found."}
end
end
defp do_eth_request(%{"params" => _params, "method" => _}) do
{:error, "Invalid params. Params must be a list."}
end
defp do_eth_request(_) do
{:error, "Method, params, and jsonrpc, are all required parameters."}
end
defp get_action(action) do
case Map.get(@methods, action) do
%{action: action} ->
{:ok, action}
_ ->
:error
end
end
defp block_param("latest"), do: {:ok, :latest}
defp block_param("earliest"), do: {:ok, :earliest}
defp block_param("pending"), do: {:ok, :pending}
defp block_param(string_integer) when is_bitstring(string_integer) do
case Integer.parse(string_integer) do
{integer, ""} -> {:ok, integer}
_ -> :error
end
end
defp block_param(nil), do: {:ok, :latest}
defp block_param(_), do: :error
def methods, do: @methods
end

@ -5,7 +5,7 @@ defmodule Explorer.Etherscan.Logs do
""" """
import Ecto.Query, only: [from: 2, where: 3, subquery: 1] import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3]
alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction} alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction}
alias Explorer.Repo alias Explorer.Repo
@ -38,6 +38,8 @@ defmodule Explorer.Etherscan.Logs do
:type :type
] ]
@default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil}
@doc """ @doc """
Gets a list of logs that meet the criteria in a given filter map. Gets a list of logs that meet the criteria in a given filter map.
@ -68,7 +70,10 @@ defmodule Explorer.Etherscan.Logs do
""" """
@spec list_logs(map()) :: [map()] @spec list_logs(map()) :: [map()]
def list_logs(%{address_hash: address_hash} = filter) when not is_nil(address_hash) do def list_logs(filter, paging_options \\ @default_paging_options)
def list_logs(%{address_hash: address_hash} = filter, paging_options) when not is_nil(address_hash) do
paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter) prepared_filter = Map.merge(@base_filter, filter)
logs_query = where_topic_match(Log, prepared_filter) logs_query = where_topic_match(Log, prepared_filter)
@ -134,14 +139,18 @@ defmodule Explorer.Etherscan.Logs do
) )
end end
Repo.all(query_with_consensus) query_with_consensus
|> order_by([log], asc: log.index)
|> page_logs(paging_options)
|> Repo.all()
end end
# Since address_hash was not present, we know that a # Since address_hash was not present, we know that a
# topic filter has been applied, so we use a different # topic filter has been applied, so we use a different
# query that is optimized for a logs filter over an # query that is optimized for a logs filter over an
# address_hash # address_hash
def list_logs(filter) do def list_logs(filter, paging_options) do
paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter) prepared_filter = Map.merge(@base_filter, filter)
logs_query = where_topic_match(Log, prepared_filter) logs_query = where_topic_match(Log, prepared_filter)
@ -182,7 +191,10 @@ defmodule Explorer.Etherscan.Logs do
select_merge: map(log, ^@log_fields) select_merge: map(log, ^@log_fields)
) )
Repo.all(query_with_block_transaction_data) query_with_block_transaction_data
|> order_by([log], asc: log.index)
|> page_logs(paging_options)
|> Repo.all()
end end
@topics [ @topics [
@ -231,4 +243,17 @@ defmodule Explorer.Etherscan.Logs do
end end
defp where_multiple_topics_match(query, _, _, _), do: query defp where_multiple_topics_match(query, _, _, _), do: query
defp page_logs(query, %{block_number: nil, transaction_index: nil, log_index: nil}) do
query
end
defp page_logs(query, %{block_number: block_number, transaction_index: transaction_index, log_index: log_index}) do
from(
data in query,
where:
data.index > ^log_index and data.block_number >= ^block_number and
data.transaction_index >= ^transaction_index
)
end
end end

@ -12,7 +12,7 @@ defmodule Explorer.Market.MarketHistoryCache do
@last_update_key :last_update @last_update_key :last_update
@history_key :history @history_key :history
# 6 hours # 6 hours
@cache_period 1_000 * 60 * 60 * 6 @cache_period Application.get_env(:explorer, __MODULE__)[:period]
@recent_days 30 @recent_days 30
def fetch do def fetch do

@ -8,7 +8,7 @@ defmodule Explorer.SmartContract.Reader do
alias EthereumJSONRPC.Contract alias EthereumJSONRPC.Contract
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Hash alias Explorer.Chain.{Hash, SmartContract}
@typedoc """ @typedoc """
Map of functions to call with the values for the function to be called with. Map of functions to call with the values for the function to be called with.
@ -34,6 +34,8 @@ defmodule Explorer.SmartContract.Reader do
@doc """ @doc """
Queries the contract functions on the blockchain and returns the call results. Queries the contract functions on the blockchain and returns the call results.
Optionally accepts the abi if it has already been fetched.
## Examples ## Examples
Note that for this example to work the database must be up to date with the Note that for this example to work the database must be up to date with the
@ -57,15 +59,21 @@ defmodule Explorer.SmartContract.Reader do
) )
# => %{"sum" => {:error, "Data overflow encoding int, data `abc` cannot fit in 256 bits"}} # => %{"sum" => {:error, "Data overflow encoding int, data `abc` cannot fit in 256 bits"}}
""" """
@spec query_verified_contract(Hash.Address.t(), functions()) :: functions_results() @spec query_verified_contract(Hash.Address.t(), functions(), SmartContract.abi() | nil) :: functions_results()
def query_verified_contract(address_hash, functions) do def query_verified_contract(address_hash, functions, mabi \\ nil) do
contract_address = Hash.to_string(address_hash) contract_address = Hash.to_string(address_hash)
abi = abi =
case mabi do
nil ->
address_hash address_hash
|> Chain.address_hash_to_smart_contract() |> Chain.address_hash_to_smart_contract()
|> Map.get(:abi) |> Map.get(:abi)
_ ->
mabi
end
query_contract(contract_address, abi, functions) query_contract(contract_address, abi, functions)
end end
@ -156,41 +164,41 @@ defmodule Explorer.SmartContract.Reader do
""" """
@spec read_only_functions(Hash.t()) :: [%{}] @spec read_only_functions(Hash.t()) :: [%{}]
def read_only_functions(contract_address_hash) do def read_only_functions(contract_address_hash) do
abi =
contract_address_hash contract_address_hash
|> Chain.address_hash_to_smart_contract() |> Chain.address_hash_to_smart_contract()
|> Map.get(:abi, []) |> Map.get(:abi)
case abi do
nil ->
[]
_ ->
abi
|> Enum.filter(& &1["constant"]) |> Enum.filter(& &1["constant"])
|> fetch_current_value_from_blockchain(contract_address_hash, []) |> Enum.map(&fetch_current_value_from_blockchain(&1, abi, contract_address_hash))
|> Enum.reverse() end
end end
def fetch_current_value_from_blockchain( defp fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
[%{"inputs" => []} = function | tail],
contract_address_hash,
acc
) do
values = values =
fetch_from_blockchain(contract_address_hash, %{ case function do
name: function["name"], %{"inputs" => []} ->
args: function["inputs"], name = function["name"]
outputs: function["outputs"] args = function["inputs"]
}) outputs = function["outputs"]
formatted = Map.replace!(function, "outputs", values) contract_address_hash
|> query_verified_contract(%{name => normalize_args(args)}, abi)
|> link_outputs_and_values(outputs, name)
fetch_current_value_from_blockchain(tail, contract_address_hash, [formatted | acc]) _ ->
link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["name"])
end end
def fetch_current_value_from_blockchain([function | tail], contract_address_hash, acc) do Map.replace!(function, "outputs", values)
values = link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["name"])
formatted = Map.replace!(function, "outputs", values)
fetch_current_value_from_blockchain(tail, contract_address_hash, [formatted | acc])
end end
def fetch_current_value_from_blockchain([], _contract_address_hash, acc), do: acc
@doc """ @doc """
Fetches the blockchain value of a function that requires arguments. Fetches the blockchain value of a function that requires arguments.
""" """
@ -201,23 +209,27 @@ defmodule Explorer.SmartContract.Reader do
@spec query_function(Hash.t(), %{name: String.t(), args: [term()]}) :: [%{}] @spec query_function(Hash.t(), %{name: String.t(), args: [term()]}) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: args}) do def query_function(contract_address_hash, %{name: name, args: args}) do
function = abi =
contract_address_hash contract_address_hash
|> Chain.address_hash_to_smart_contract() |> Chain.address_hash_to_smart_contract()
|> Map.get(:abi, []) |> Map.get(:abi)
outputs =
case abi do
nil ->
nil
_ ->
function =
abi
|> Enum.filter(fn function -> function["name"] == name end) |> Enum.filter(fn function -> function["name"] == name end)
|> List.first() |> List.first()
fetch_from_blockchain(contract_address_hash, %{ function["outputs"]
name: name,
args: args,
outputs: function["outputs"]
})
end end
defp fetch_from_blockchain(contract_address_hash, %{name: name, args: args, outputs: outputs}) do
contract_address_hash contract_address_hash
|> query_verified_contract(%{name => normalize_args(args)}) |> query_verified_contract(%{name => normalize_args(args)}, abi)
|> link_outputs_and_values(outputs, name) |> link_outputs_and_values(outputs, name)
end end

@ -630,7 +630,7 @@ defmodule Explorer.ChainTest do
assert Repo.aggregate(Transaction, :count, :hash) == 0 assert Repo.aggregate(Transaction, :count, :hash) == 0
assert [] = Chain.block_to_transactions(block) assert [] = Chain.block_to_transactions(block.hash)
end end
test "with transactions" do test "with transactions" do
@ -639,7 +639,7 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block) assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block.hash)
end end
test "with transactions can be paginated by {index}" do test "with transactions can be paginated by {index}" do
@ -657,7 +657,7 @@ defmodule Explorer.ChainTest do
|> with_block(block) |> with_block(block)
assert second_page_hashes == assert second_page_hashes ==
block block.hash
|> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50}) |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50})
|> Enum.map(& &1.hash) |> Enum.map(& &1.hash)
|> Enum.reverse() |> Enum.reverse()
@ -683,7 +683,7 @@ defmodule Explorer.ChainTest do
token: token token: token
) )
fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block)) fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block.hash))
assert fetched_transaction.hash == transaction.hash assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2 assert length(fetched_transaction.token_transfers) == 2
end end
@ -693,7 +693,7 @@ defmodule Explorer.ChainTest do
test "without transactions" do test "without transactions" do
block = insert(:block) block = insert(:block)
assert Chain.block_to_transaction_count(block) == 0 assert Chain.block_to_transaction_count(block.hash) == 0
end end
test "with transactions" do test "with transactions" do
@ -702,7 +702,7 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
assert Chain.block_to_transaction_count(block) == 1 assert Chain.block_to_transaction_count(block.hash) == 1
end end
end end
@ -2090,7 +2090,7 @@ defmodule Explorer.ChainTest do
test "with transaction without internal transactions" do test "with transaction without internal transactions" do
transaction = insert(:transaction) transaction = insert(:transaction)
assert [] = Chain.transaction_to_internal_transactions(transaction) assert [] = Chain.transaction_to_internal_transactions(transaction.hash)
end end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
@ -2117,7 +2117,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction) results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction.hash)
assert 2 == length(results) assert 2 == length(results)
@ -2151,7 +2151,7 @@ defmodule Explorer.ChainTest do
to_address: %Ecto.Association.NotLoaded{}, to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{block: %Ecto.Association.NotLoaded{}} transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
} }
] = Chain.transaction_to_internal_transactions(transaction) ] = Chain.transaction_to_internal_transactions(transaction.hash)
assert [ assert [
%InternalTransaction{ %InternalTransaction{
@ -2161,7 +2161,7 @@ defmodule Explorer.ChainTest do
} }
] = ] =
Chain.transaction_to_internal_transactions( Chain.transaction_to_internal_transactions(
transaction, transaction.hash,
necessity_by_association: %{ necessity_by_association: %{
:from_address => :optional, :from_address => :optional,
:to_address => :optional, :to_address => :optional,
@ -2183,7 +2183,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
result = Chain.transaction_to_internal_transactions(transaction) result = Chain.transaction_to_internal_transactions(transaction.hash)
assert Enum.empty?(result) assert Enum.empty?(result)
end end
@ -2202,7 +2202,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end end
@ -2222,7 +2222,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end end
@ -2243,7 +2243,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end end
@ -2271,7 +2271,7 @@ defmodule Explorer.ChainTest do
) )
result = result =
transaction transaction.hash
|> Chain.transaction_to_internal_transactions() |> Chain.transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index}) |> Enum.map(&{&1.transaction_hash, &1.index})
@ -2301,17 +2301,17 @@ defmodule Explorer.ChainTest do
) )
assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] == assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] ==
transaction transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2}) |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index}) |> Enum.map(&{&1.transaction_hash, &1.index})
assert [{first_transaction_hash, first_index}] == assert [{first_transaction_hash, first_index}] ==
transaction transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1}) |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1})
|> Enum.map(&{&1.transaction_hash, &1.index}) |> Enum.map(&{&1.transaction_hash, &1.index})
assert [{second_transaction_hash, second_index}] == assert [{second_transaction_hash, second_index}] ==
transaction transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, page_size: 2}) |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index}) |> Enum.map(&{&1.transaction_hash, &1.index})
end end
@ -2321,7 +2321,7 @@ defmodule Explorer.ChainTest do
test "without logs" do test "without logs" do
transaction = insert(:transaction) transaction = insert(:transaction)
assert [] = Chain.transaction_to_logs(transaction) assert [] = Chain.transaction_to_logs(transaction.hash)
end end
test "with logs" do test "with logs" do
@ -2332,7 +2332,7 @@ defmodule Explorer.ChainTest do
%Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction) %Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction)
assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction) assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash)
end end
test "with logs can be paginated" do test "with logs can be paginated" do
@ -2349,7 +2349,7 @@ defmodule Explorer.ChainTest do
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
assert second_page_indexes == assert second_page_indexes ==
transaction transaction.hash
|> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50}) |> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50})
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
end end
@ -2364,7 +2364,7 @@ defmodule Explorer.ChainTest do
assert [%Log{address: %Address{}, transaction: %Transaction{}}] = assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs( Chain.transaction_to_logs(
transaction, transaction.hash,
necessity_by_association: %{ necessity_by_association: %{
address: :optional, address: :optional,
transaction: :optional transaction: :optional
@ -2376,7 +2376,7 @@ defmodule Explorer.ChainTest do
address: %Ecto.Association.NotLoaded{}, address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{} transaction: %Ecto.Association.NotLoaded{}
} }
] = Chain.transaction_to_logs(transaction) ] = Chain.transaction_to_logs(transaction.hash)
end end
end end
@ -2384,7 +2384,7 @@ defmodule Explorer.ChainTest do
test "without token transfers" do test "without token transfers" do
transaction = insert(:transaction) transaction = insert(:transaction)
assert [] = Chain.transaction_to_token_transfers(transaction) assert [] = Chain.transaction_to_token_transfers(transaction.hash)
end end
test "with token transfers" do test "with token transfers" do
@ -2397,7 +2397,7 @@ defmodule Explorer.ChainTest do
insert(:token_transfer, transaction: transaction) insert(:token_transfer, transaction: transaction)
assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] = assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] =
Chain.transaction_to_token_transfers(transaction) Chain.transaction_to_token_transfers(transaction.hash)
end end
test "token transfers necessity_by_association loads associations" do test "token transfers necessity_by_association loads associations" do
@ -2410,7 +2410,7 @@ defmodule Explorer.ChainTest do
assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] = assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] =
Chain.transaction_to_token_transfers( Chain.transaction_to_token_transfers(
transaction, transaction.hash,
necessity_by_association: %{ necessity_by_association: %{
token: :optional, token: :optional,
transaction: :optional transaction: :optional
@ -2422,7 +2422,7 @@ defmodule Explorer.ChainTest do
token: %Ecto.Association.NotLoaded{}, token: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{} transaction: %Ecto.Association.NotLoaded{}
} }
] = Chain.transaction_to_token_transfers(transaction) ] = Chain.transaction_to_token_transfers(transaction.hash)
end end
end end
@ -2480,7 +2480,17 @@ defmodule Explorer.ChainTest do
insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil, names: []) insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil, names: [])
|> Repo.preload([:contracts_creation_internal_transaction, :contracts_creation_transaction, :token]) |> Repo.preload([:contracts_creation_internal_transaction, :contracts_creation_transaction, :token])
response = Chain.find_contract_address(address.hash) options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
response = Chain.find_contract_address(address.hash, options, true)
assert response == {:ok, address} assert response == {:ok, address}
end end
@ -2523,11 +2533,11 @@ defmodule Explorer.ChainTest do
|> Decimal.add(Decimal.new(3)) |> Decimal.add(Decimal.new(3))
|> Wei.from(:wei) |> Wei.from(:wei)
assert expected == Chain.block_reward(block) assert expected == Chain.block_reward(block.number)
end end
test "with block without transactions", %{block: block, emission_reward: emission_reward} do test "with block without transactions", %{block: block, emission_reward: emission_reward} do
assert emission_reward.reward == Chain.block_reward(block) assert emission_reward.reward == Chain.block_reward(block.number)
end end
end end

@ -158,6 +158,46 @@ defmodule Explorer.Etherscan.LogsTest do
assert found_log.transaction_hash == transaction_block1.hash assert found_log.transaction_hash == transaction_block1.hash
end end
test "paginates logs" do
contract_address = insert(:contract_address)
transaction =
%Transaction{block: block} =
:transaction
|> insert(to_address: contract_address)
|> with_block()
inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction)
filter = %{
from_block: block.number,
to_block: block.number,
address_hash: contract_address.hash
}
first_found_logs = Logs.list_logs(filter)
assert Enum.count(first_found_logs) == 1_000
last_record = List.last(first_found_logs)
next_page_params = %{
log_index: last_record.index,
transaction_index: last_record.transaction_index,
block_number: transaction.block_number
}
second_found_logs = Logs.list_logs(filter, next_page_params)
assert Enum.count(second_found_logs) == 1_000
all_found_logs = first_found_logs ++ second_found_logs
assert Enum.all?(inserted_records, fn record ->
Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end)
end)
end
test "with a valid topic{x}" do test "with a valid topic{x}" do
contract_address = insert(:contract_address) contract_address = insert(:contract_address)

@ -102,7 +102,7 @@ defmodule Explorer.SmartContract.ReaderTest do
end end
end end
describe "query_verified_contract/2" do describe "query_verified_contract/3" do
test "correctly returns the results of the smart contract functions" do test "correctly returns the results of the smart contract functions" do
hash = hash =
:smart_contract :smart_contract

Loading…
Cancel
Save