Merge branch 'master' into ab-skip-transaction-keys

pull/2387/head
Victor Baranov 5 years ago committed by GitHub
commit 3dd92b823b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 20
      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. 14
      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. 104
      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
  81. 2
      docker/Dockerfile

@ -1,12 +1,21 @@
## Current
### Features
- [#2391](https://github.com/poanetwork/blockscout/pull/2391) - Controllers Improvements
- [#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
- [#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
- [#2324](https://github.com/poanetwork/blockscout/pull/2324) - set timeout for loading message on the main page
### 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)
- [#2387](https://github.com/poanetwork/blockscout/pull/2387) - fix not existing keys in transaction json rpc
- [#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
@ -25,6 +34,8 @@
- [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments
### 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
- [#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

@ -1,4 +1,3 @@
import _ from 'lodash'
import { reducer, initialState } from '../../js/pages/pending_transactions'
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 {
min-width: fit-content;
width: 100%;

@ -339,9 +339,6 @@ $tile-body-a-color: #5959d8 !default;
padding-left: 6px;
padding-right: 6px;
}
.tile-type-block {
overflow: hidden;
}
}
.row {
@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 'lodash'
import map from 'lodash/map'
import merge from 'lodash/merge'
import URI from 'urijs'
import humps from 'humps'
import listMorph from '../lib/list_morph'
@ -164,7 +165,7 @@ export const elements = {
if (state.itemKey) {
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 })
return
}
@ -244,7 +245,7 @@ export const elements = {
* adding or removing with the correct animation. Check list_morph.js for more informantion.
*/
export function createAsyncLoadStore (reducer, initialState, itemKey) {
const state = _.merge(asyncInitialState, initialState)
const state = merge(asyncInitialState, initialState)
const store = createStore(reduceReducers(asyncReducer, reducer, state))
if (typeof itemKey !== 'undefined') {

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

@ -1,5 +1,10 @@
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 { updateAllAges } from './from_now'
@ -25,12 +30,12 @@ import { updateAllAges } from './from_now'
export default function (container, newElements, { key, horizontal } = {}) {
if (!container) return
const oldElements = $(container).children().get()
let currentList = _.map(oldElements, (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] }))
let currentList = map(oldElements, (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] }))
// remove old items
const removals = _.differenceBy(currentList, newList, 'id')
const removals = differenceBy(currentList, newList, 'id')
let canAnimate = !horizontal && removals.length <= 1
removals.forEach(({ el }) => {
if (!canAnimate) return el.remove()
@ -38,7 +43,7 @@ export default function (container, newElements, { key, horizontal } = {}) {
$el.addClass('shrink-out')
setTimeout(() => { slideUpRemove($el) }, 400)
})
currentList = _.differenceBy(currentList, removals, 'id')
currentList = differenceBy(currentList, removals, 'id')
// update kept items
currentList = currentList.map(({ el }, i) => ({
@ -47,14 +52,14 @@ export default function (container, newElements, { key, horizontal } = {}) {
}))
// 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
finalList.forEach((el, i) => {
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
if (!_.get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el)
slideDownBefore($(_.get(finalList, `[${i - 1}]`)), el)
if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), 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
const originalScrollHeight = document.body.scrollHeight
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
const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY

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

@ -1,5 +1,7 @@
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'
/**
@ -97,17 +99,17 @@ export function createStore (reducer) {
*/
export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) {
function loadElements () {
return _.reduce(elements, (pageLoadParams, { load }, selector) => {
return reduce(elements, (pageLoadParams, { load }, selector) => {
if (!load) return pageLoadParams
const $el = $(selector)
if (!$el.length) return pageLoadParams
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) {
_.forIn(elements, ({ render }, selector) => {
forIn(elements, ({ render }, selector) => {
if (!render) return
const $el = $(selector)
if (!$el.length) return

@ -1,8 +1,8 @@
import _ from 'lodash'
import debounce from 'lodash/debounce'
export function batchChannel (func) {
let msgs = []
const debouncedFunc = _.debounce(() => {
const debouncedFunc = debounce(() => {
func.apply(this, [msgs])
msgs = []
}, 1000, { maxWait: 5000 })
@ -11,3 +11,16 @@ export function batchChannel (func) {
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 'lodash'
import omit from 'lodash/omit'
import URI from 'urijs'
import humps from 'humps'
import numeral from 'numeral'
@ -25,7 +25,7 @@ export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type'))
return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state

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

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

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

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

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

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

@ -1,11 +1,15 @@
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 numeral from 'numeral'
import socket from '../socket'
import { exchangeRateChannel, formatUsdValue } from '../lib/currency'
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 { createMarketHistoryChart } from '../lib/market_history_chart'
@ -33,7 +37,7 @@ export const reducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type'))
return Object.assign({}, state, omit(action, 'type'))
}
case 'RECEIVED_NEW_ADDRESS_COUNT': {
return Object.assign({}, state, {
@ -122,12 +126,12 @@ function withMissingBlocks (reducer) {
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)
return Object.assign({}, result, {
blocks: _.rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => _.find(result.blocks, ['blockNumber', blockNumber]) || {
blocks: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || {
blockNumber,
chainBlockHtml: placeHolderBlock(blockNumber)
})
@ -194,7 +198,7 @@ const elements = {
const container = $el[0]
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 })
}
}
@ -210,11 +214,7 @@ const elements = {
},
'[data-selector="chain-block-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) {
if (state.blocksLoading) {
$el.show()
} else {
$el.hide()
}
showLoader(state.blocksLoading, $el)
}
},
'[data-selector="transactions-list"] [data-selector="error-message"]': {
@ -224,7 +224,7 @@ const elements = {
},
'[data-selector="transactions-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) {
$el.toggle(state.transactionsLoading)
showLoader(state.transactionsLoading, $el)
}
},
'[data-selector="transactions-list"]': {
@ -234,7 +234,7 @@ const elements = {
render ($el, state, oldState) {
if (oldState.transactions === state.transactions) return
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' })
}
},

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

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

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

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

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

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController 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),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
:ok <- Chain.check_address_exists(address_hash) do
full_options = paging_options(params)
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(
conn,
:index,
address,
address_hash,
Map.delete(next_page_params, "type")
)
end
@ -52,7 +52,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
:not_found ->
not_found(conn)
end
end

@ -8,8 +8,18 @@ defmodule BlockScoutWeb.AddressContractController do
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render(
conn,
"index.html",

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressLogsController 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),
{: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))
{results, next_page} = split_list_by_page(logs_plus_one)
@ -26,7 +26,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil
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
items =
@ -74,7 +74,7 @@ defmodule BlockScoutWeb.AddressLogsController 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),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
:ok <- Chain.check_address_exists(address_hash) do
topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil
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
items =

@ -15,8 +15,18 @@ defmodule BlockScoutWeb.AddressReadContractController do
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render(
conn,
"index.html",

@ -8,9 +8,8 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
{: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, block_options) do
reward = Chain.block_reward(block)
{:ok, block} <- Chain.number_to_block(block_number) do
reward = Chain.block_reward(block_number)
render(conn, :block_reward, block: block, reward: reward)
else

@ -1,44 +1,10 @@
defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller
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.
""",
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
alias Explorer.EthRPC
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
responses = responses(requests)
responses = EthRPC.responses(requests)
conn
|> put_status(200)
@ -46,7 +12,7 @@ defmodule BlockScoutWeb.API.RPC.EthController do
end
def eth_request(%{body_params: %{"_json" => request}} = conn, _) do
[response] = responses([request])
[response] = EthRPC.responses([request])
conn
|> put_status(200)
@ -65,297 +31,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do
_ -> request
end
[response] = responses([decoded_request])
[response] = EthRPC.responses([decoded_request])
conn
|> put_status(200)
|> render("response.json", %{response: response})
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

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
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)
render(conn, :gettxinfo, %{

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

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

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

@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
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)
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
:rewards => :optional
}
) do
block_transaction_count = Chain.block_to_transaction_count(block)
block_transaction_count = Chain.block_to_transaction_count(block.hash)
render(
conn,

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

@ -33,7 +33,7 @@ defmodule BlockScoutWeb.SmartContractController do
def show(conn, params) do
with true <- ajax?(conn),
{: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 =
Reader.query_function(
address_hash,
@ -51,7 +51,7 @@ defmodule BlockScoutWeb.SmartContractController do
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
:not_found ->
not_found(conn)
_ ->

@ -62,15 +62,15 @@ defmodule BlockScoutWeb.TransactionController do
def show(conn, %{"id" => id}) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id),
{:ok, %Chain.Transaction{} = transaction} <- Chain.hash_to_transaction(transaction_hash) do
if Chain.transaction_has_token_transfers?(transaction.hash) do
:ok <- Chain.check_transaction_exists(transaction_hash) do
if Chain.transaction_has_token_transfers?(transaction_hash) do
redirect(conn, to: transaction_token_transfer_path(conn, :index, id))
else
redirect(conn, to: transaction_internal_transaction_path(conn, :index, id))
end
else
: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

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
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 =
Keyword.merge(
[
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
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)
@ -37,7 +37,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
transaction_internal_transaction_path(
conn,
:index,
transaction,
hash,
Map.delete(next_page_params, "type")
)
end
@ -66,7 +66,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
|> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} ->
:not_found ->
conn
|> put_status(404)
|> put_view(TransactionView)

@ -11,7 +11,9 @@ defmodule BlockScoutWeb.TransactionLogController 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),
{: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 =
Keyword.merge(
[
@ -22,7 +24,7 @@ defmodule BlockScoutWeb.TransactionLogController do
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)

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

@ -10,8 +10,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
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 =
Keyword.merge(
[
@ -24,7 +23,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
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)
@ -34,7 +33,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
nil
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
items =
@ -62,7 +61,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
|> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} ->
:not_found ->
conn
|> put_status(404)
|> put_view(TransactionView)

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

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

@ -40,7 +40,9 @@
</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 %>

@ -46,14 +46,18 @@
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.evm_version %></dd>
</dl>
<% end %>
<%= if @address.smart_contract.constructor_arguments do %>
<dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Constructor arguments" %></dt>
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.constructor_arguments %></dd>
</dl>
<% end %>
</div>
<hr/>
<%= if @address.smart_contract.constructor_arguments do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Constructor Arguments" %></h3>
</div>
<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>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract source code" %></h3>
@ -116,7 +120,7 @@
<h3><%= gettext "External libraries" %></h3>
</div>
<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>
</div>
</section>

@ -66,7 +66,9 @@
</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 %>

@ -27,7 +27,9 @@
</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 %>

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

@ -21,7 +21,9 @@
</span>
</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 %>

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

@ -22,7 +22,9 @@
<%= gettext "Something went wrong, click to reload." %>
</span>
</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 %>

@ -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 %>
<div data-items></div>
<div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div data-empty-response-message style="display: none;">
<span><%= gettext "There are no blocks." %></span>
</div>

@ -29,7 +29,9 @@
</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 %>

@ -1,5 +1,172 @@
<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-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(
@block,
class: "tile-title",

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

@ -28,7 +28,9 @@
</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 %>

@ -27,7 +27,9 @@
</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 %>

@ -26,7 +26,9 @@
</span>
</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 %>

@ -28,7 +28,9 @@
</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 %>

@ -16,7 +16,9 @@
</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 %>
</div>

@ -19,7 +19,9 @@
</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 %>

@ -18,7 +18,9 @@
</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 %>

@ -1,6 +1,7 @@
defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain.{Address, Data, InternalTransaction}
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(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
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

@ -34,7 +34,7 @@ msgstr ""
#, elixir-format
#: 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"
msgstr ""
@ -258,7 +258,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, 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"
msgstr ""
@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr ""
#, 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"
msgstr ""
@ -522,7 +522,7 @@ msgstr ""
#, elixir-format
#: 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/chain/_block.html.eex:15
#: lib/block_scout_web/templates/chain/_block.html.eex:182
msgid "Miner"
msgstr ""
@ -1001,7 +1001,7 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:22
#: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false"
msgstr ""
@ -1019,7 +1019,7 @@ msgid "string"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:21
#: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true"
msgstr ""
@ -1040,7 +1040,7 @@ msgstr ""
#, elixir-format
#: 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
msgid "Reward"
msgstr ""
@ -1058,7 +1058,6 @@ msgstr ""
#, elixir-format
#: 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:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..."
msgstr ""
@ -1078,11 +1077,6 @@ msgstr ""
msgid "GraphQL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
msgid "Loading"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:73
msgid "RPC"
@ -1277,7 +1271,7 @@ msgid "There are no pending transactions."
msgstr ""
#, 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."
msgstr ""
@ -1407,17 +1401,17 @@ msgid "Support"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
@ -1587,27 +1581,27 @@ msgid "Block Details"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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."
msgstr ""
#, 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"
msgstr ""
#, 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."
msgstr ""
@ -1745,8 +1739,8 @@ msgid "here."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_token/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV"
msgstr ""
@ -1755,11 +1749,6 @@ msgstr ""
msgid "Change Network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:51
msgid "Constructor arguments"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
@ -1771,7 +1760,7 @@ msgid "ERC-721 "
msgstr ""
#, 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"
msgstr ""
@ -1804,3 +1793,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
msgid "Connection Lost"
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
#: 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"
msgstr ""
@ -258,7 +258,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, 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"
msgstr ""
@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr ""
#, 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"
msgstr ""
@ -522,7 +522,7 @@ msgstr ""
#, elixir-format
#: 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/chain/_block.html.eex:15
#: lib/block_scout_web/templates/chain/_block.html.eex:182
msgid "Miner"
msgstr ""
@ -1001,7 +1001,7 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:22
#: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false"
msgstr ""
@ -1019,7 +1019,7 @@ msgid "string"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:21
#: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true"
msgstr ""
@ -1040,7 +1040,7 @@ msgstr ""
#, elixir-format
#: 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
msgid "Reward"
msgstr ""
@ -1058,7 +1058,6 @@ msgstr ""
#, elixir-format
#: 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:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..."
msgstr ""
@ -1078,11 +1077,6 @@ msgstr ""
msgid "GraphQL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
msgid "Loading"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:73
msgid "RPC"
@ -1277,7 +1271,7 @@ msgid "There are no pending transactions."
msgstr ""
#, 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."
msgstr ""
@ -1407,17 +1401,17 @@ msgid "Support"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
@ -1587,27 +1581,27 @@ msgid "Block Details"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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."
msgstr ""
#, 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"
msgstr ""
#, 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."
msgstr ""
@ -1745,8 +1739,8 @@ msgid "here."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_token/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV"
msgstr ""
@ -1755,11 +1749,6 @@ msgstr ""
msgid "Change Network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:51
msgid "Constructor arguments"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
@ -1771,7 +1760,7 @@ msgid "ERC-721 "
msgstr ""
#, 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"
msgstr ""
@ -1804,3 +1793,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
msgid "Connection Lost"
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"))
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", %{
conn: conn,
api_params: api_params

@ -17,7 +17,15 @@ config :explorer,
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)
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
@ -106,6 +114,14 @@ config :spandex_ecto, SpandexEcto.EctoLogger,
tracer: Explorer.Tracer,
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
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

@ -364,8 +364,8 @@ defmodule Explorer.Chain do
Uncles are not currently accounted for.
"""
@spec block_reward(Block.t()) :: Wei.t()
def block_reward(%Block{number: block_number}) do
@spec block_reward(Block.block_number()) :: Wei.t()
def block_reward(block_number) do
query =
from(
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
the `index` that are passed.
"""
@spec block_to_transactions(Block.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
def block_to_transactions(%Block{hash: block_hash}, options \\ []) when is_list(options) do
@spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
def block_to_transactions(block_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options
@ -432,8 +432,8 @@ defmodule Explorer.Chain do
@doc """
Counts the number of `t:Explorer.Chain.Transaction.t/0` in the `block`.
"""
@spec block_to_transaction_count(Block.t()) :: non_neg_integer()
def block_to_transaction_count(%Block{hash: block_hash}) do
@spec block_to_transaction_count(Hash.Full.t()) :: non_neg_integer()
def block_to_transaction_count(block_hash) do
query =
from(
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, :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}
def get_balance_as_of_block(address, block) when is_integer(block) do
coin_balance_query =
@ -930,33 +930,44 @@ defmodule Explorer.Chain do
Repo.all(query)
end
@spec find_contract_address(Hash.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
@doc """
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 =
from(
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)
)
query_with_decompiled_flag = with_decompiled_code_flag(query, hash)
address = Repo.one(query_with_decompiled_flag)
if address do
{:ok, address}
else
{:error, :not_found}
query
|> join_associations(necessity_by_association)
|> with_decompiled_code_flag(hash, query_decompiled_code_flag)
|> Repo.one()
|> case do
nil -> {:error, :not_found}
address -> {:ok, address}
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
query =
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()
]
def transaction_to_internal_transactions(
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash},
options \\ []
)
when is_list(options) do
def transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -2244,12 +2251,8 @@ defmodule Explorer.Chain do
the `index` that are passed.
"""
@spec transaction_to_logs(Transaction.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
def transaction_to_logs(
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
options \\ []
)
when is_list(options) do
@spec transaction_to_logs(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
def transaction_to_logs(transaction_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -2276,14 +2279,10 @@ defmodule Explorer.Chain do
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()
]
def transaction_to_token_transfers(
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
options \\ []
)
when is_list(options) do
def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
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])
end
@spec address_hash_to_address_with_source_code(%Explorer.Chain.Hash{}) :: %Explorer.Chain.Address{} | nil
def address_hash_to_address_with_source_code(%Explorer.Chain.Hash{} = address_hash) do
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do
nil -> nil
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
end
end
@spec address_hash_to_smart_contract(%Explorer.Chain.Hash{}) :: %Explorer.Chain.SmartContract{} | nil
def address_hash_to_smart_contract(%Explorer.Chain.Hash{} = address_hash) do
@spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil
def address_hash_to_smart_contract(address_hash) do
query =
from(
smart_contract in SmartContract,
@ -3276,8 +3275,6 @@ defmodule Explorer.Chain do
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, true) do
@ -3300,4 +3297,203 @@ defmodule Explorer.Chain do
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)
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

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

@ -11,7 +11,7 @@ defmodule Explorer.Counters.AverageBlockTime do
alias Explorer.Repo
alias Timex.Duration
@refresh_period 30 * 60 * 1_000
@refresh_period Application.get_env(:explorer, __MODULE__)[:period]
@doc """
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.Repo
@ -38,6 +38,8 @@ defmodule Explorer.Etherscan.Logs do
:type
]
@default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil}
@doc """
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()]
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)
logs_query = where_topic_match(Log, prepared_filter)
@ -134,14 +139,18 @@ defmodule Explorer.Etherscan.Logs do
)
end
Repo.all(query_with_consensus)
query_with_consensus
|> order_by([log], asc: log.index)
|> page_logs(paging_options)
|> Repo.all()
end
# Since address_hash was not present, we know that a
# topic filter has been applied, so we use a different
# query that is optimized for a logs filter over an
# 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)
logs_query = where_topic_match(Log, prepared_filter)
@ -182,7 +191,10 @@ defmodule Explorer.Etherscan.Logs do
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
@topics [
@ -231,4 +243,17 @@ defmodule Explorer.Etherscan.Logs do
end
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

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

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

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

@ -158,6 +158,46 @@ defmodule Explorer.Etherscan.LogsTest do
assert found_log.transaction_hash == transaction_block1.hash
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
contract_address = insert(:contract_address)

@ -102,7 +102,7 @@ defmodule Explorer.SmartContract.ReaderTest do
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
hash =
:smart_contract

@ -1,4 +1,4 @@
FROM bitwalker/alpine-elixir-phoenix
FROM bitwalker/alpine-elixir-phoenix:1.9.0
RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python

Loading…
Cancel
Save