diff --git a/CHANGELOG.md b/CHANGELOG.md index 783de20494..c53cc0f239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js index e9f7fc941d..ddbf84ce7d 100644 --- a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js +++ b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js @@ -1,4 +1,3 @@ -import _ from 'lodash' import { reducer, initialState } from '../../js/pages/pending_transactions' test('CHANNEL_DISCONNECTED', () => { diff --git a/apps/block_scout_web/assets/css/components/_stakes_table.scss b/apps/block_scout_web/assets/css/components/_stakes_table.scss index 79cdc20b38..1831dc4d5b 100644 --- a/apps/block_scout_web/assets/css/components/_stakes_table.scss +++ b/apps/block_scout_web/assets/css/components/_stakes_table.scss @@ -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%; diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index 8965715727..501533ce05 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -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); + } +} diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index 8610d901f6..8477a7d728 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -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') { diff --git a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js index 45b278ae4d..b7fbb940a2 100644 --- a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js +++ b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js @@ -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, { diff --git a/apps/block_scout_web/assets/js/lib/list_morph.js b/apps/block_scout_web/assets/js/lib/list_morph.js index 175acb7c3f..6cdfea3e3f 100644 --- a/apps/block_scout_web/assets/js/lib/list_morph.js +++ b/apps/block_scout_web/assets/js/lib/list_morph.js @@ -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 diff --git a/apps/block_scout_web/assets/js/lib/market_history_chart.js b/apps/block_scout_web/assets/js/lib/market_history_chart.js index 615f888d8a..d54d998580 100644 --- a/apps/block_scout_web/assets/js/lib/market_history_chart.js +++ b/apps/block_scout_web/assets/js/lib/market_history_chart.js @@ -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 } diff --git a/apps/block_scout_web/assets/js/lib/redux_helpers.js b/apps/block_scout_web/assets/js/lib/redux_helpers.js index fdf7c659b3..35dcc65b2c 100644 --- a/apps/block_scout_web/assets/js/lib/redux_helpers.js +++ b/apps/block_scout_web/assets/js/lib/redux_helpers.js @@ -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 diff --git a/apps/block_scout_web/assets/js/lib/utils.js b/apps/block_scout_web/assets/js/lib/utils.js index d3bd6e84d1..e00d06ec1b 100644 --- a/apps/block_scout_web/assets/js/lib/utils.js +++ b/apps/block_scout_web/assets/js/lib/utils.js @@ -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 + } +} diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 5b35a0b9d7..ddafbcb1e2 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -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 diff --git a/apps/block_scout_web/assets/js/pages/address/coin_balances.js b/apps/block_scout_web/assets/js/pages/address/coin_balances.js index 8c0100d3d2..b4523a6c64 100644 --- a/apps/block_scout_web/assets/js/pages/address/coin_balances.js +++ b/apps/block_scout_web/assets/js/pages/address/coin_balances.js @@ -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 diff --git a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js index aa95278c71..6c3d503256 100644 --- a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js +++ b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js @@ -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 diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js index 5b4cf8aecd..5c9bed9da2 100644 --- a/apps/block_scout_web/assets/js/pages/address/logs.js +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -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}) diff --git a/apps/block_scout_web/assets/js/pages/address/transactions.js b/apps/block_scout_web/assets/js/pages/address/transactions.js index be70dcc3ba..ecd604e2d5 100644 --- a/apps/block_scout_web/assets/js/pages/address/transactions.js +++ b/apps/block_scout_web/assets/js/pages/address/transactions.js @@ -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 diff --git a/apps/block_scout_web/assets/js/pages/address/validations.js b/apps/block_scout_web/assets/js/pages/address/validations.js index 6aca07a98b..d2456a183e 100644 --- a/apps/block_scout_web/assets/js/pages/address/validations.js +++ b/apps/block_scout_web/assets/js/pages/address/validations.js @@ -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 }) diff --git a/apps/block_scout_web/assets/js/pages/blocks.js b/apps/block_scout_web/assets/js/pages/blocks.js index 2103c18a89..4d9bdc05d9 100644 --- a/apps/block_scout_web/assets/js/pages/blocks.js +++ b/apps/block_scout_web/assets/js/pages/blocks.js @@ -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)) }) } diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index f4cd0e6628..df89d92f36 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -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' }) } }, diff --git a/apps/block_scout_web/assets/js/pages/pending_transactions.js b/apps/block_scout_web/assets/js/pages/pending_transactions.js index 1cbb5d47d0..711f7c4343 100644 --- a/apps/block_scout_web/assets/js/pages/pending_transactions.js +++ b/apps/block_scout_web/assets/js/pages/pending_transactions.js @@ -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, { diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index cd247d530a..139d1f527c 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -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) { diff --git a/apps/block_scout_web/assets/js/pages/transactions.js b/apps/block_scout_web/assets/js/pages/transactions.js index a6c038166b..f528f760c4 100644 --- a/apps/block_scout_web/assets/js/pages/transactions.js +++ b/apps/block_scout_web/assets/js/pages/transactions.js @@ -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, { diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js index 619a8e1326..10504c144c 100644 --- a/apps/block_scout_web/assets/js/pages/verification_form.js +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -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 diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index 08a2f410e7..d7d504678d 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -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) { diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex index 33a01959c9..9267c0dcab 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex index bc4df8c4be..5db900b12b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -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", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex index e349ecd9ef..9e05643187 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -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 = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex index d57aa30807..0849689dce 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex @@ -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", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index 91569a96da..835e19bf3e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex index 028084e73f..26ffd5e47f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex index 344dbe1037..565ea4b6a5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -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, %{ diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex index 1eec849380..7b783b9761 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex index 50334a1a45..8b3d3d71eb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex index 9309884d5c..4a658c409e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex index 15b06f1edd..a1e4cbeb09 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex @@ -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, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 40a0fd3bbb..2c16cd1b03 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -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: diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 70ec6219f3..311feb4591 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -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) _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 1bedf97c1d..3f849c0e7c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 159d144cdd..ca154a0136 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -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) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex index 0a87d12493..e076b9ee7b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex @@ -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) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex index 3d090e8a9b..250a6b4442 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex @@ -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, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex index cfb215da3e..585a01301f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex @@ -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) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex index 9680faa2a8..56f1a2bbb2 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex @@ -21,7 +21,7 @@ <%= if @total_supply do %> - (<%= balance_percentage(@address, @total_supply) %>) + <%= balance_percentage(@address, @total_supply) %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex index 3aaab46ad5..c594915d9f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex @@ -38,7 +38,7 @@ - + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex index 6b9459f5b3..2e71dd22ca 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex @@ -40,7 +40,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 340765a97f..e8d6501e02 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -46,14 +46,18 @@
<%= @address.smart_contract.evm_version %>
<% end %> - <%= if @address.smart_contract.constructor_arguments do %> -
-
<%= gettext "Constructor arguments" %>
-
<%= @address.smart_contract.constructor_arguments %>
-
- <% end %> -
+ <%= if @address.smart_contract.constructor_arguments do %> +
+
+

<%= gettext "Constructor Arguments" %>

+
+
+
<%= raw(format_constructor_arguments(@address.smart_contract)) %>
+              
+
+
+ <% end %>

<%= gettext "Contract source code" %>

@@ -116,7 +120,7 @@

<%= gettext "External libraries" %>

-
<%= format_external_libraries(@address.smart_contract.external_libraries) %>
+              
<%= raw(format_external_libraries(@address.smart_contract.external_libraries)) %>
               
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex index 3bc2e7fe9b..f18638db0d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -66,7 +66,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex index 3a44e0cb2d..fabbc7f7d8 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex @@ -27,7 +27,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index 840d9cc1da..8510bdca64 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -19,7 +19,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex index c2ab502bf0..738d70883d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex @@ -21,7 +21,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index c3a4c94129..83c5454627 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -65,7 +65,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex index 3f85f03aba..03b37369bc 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex @@ -22,7 +22,9 @@ <%= gettext "Something went wrong, click to reload." %> -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex index 92b91ce287..dd8a3ed093 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex @@ -11,7 +11,9 @@ <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= gettext "There are no blocks." %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex index a497ab1b7e..e0847cac86 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex @@ -29,7 +29,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex index 706d18ec47..729df59a3c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex @@ -1,5 +1,172 @@
+
+
+ Block Validated, processing... +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+
<%= link( @block, class: "tile-title", diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 0c29001697..69d1f8b6ed 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -5,7 +5,7 @@
-
+ -
-
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex index da834dbc88..1e78b11353 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex @@ -27,7 +27,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex index 980cc4b05a..addaa0c359 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex @@ -26,7 +26,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex index 18928915f2..bce59eb00b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex @@ -28,7 +28,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex index e6623b4061..70162a3be8 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex @@ -16,7 +16,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex index 3c84e7c4f9..17f480b439 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex @@ -19,7 +19,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex index d40dedd414..8d356a450a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex @@ -18,7 +18,9 @@ -
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> +
<%= 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 %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 5de263f820..57c6e11da7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -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}] (#{type}) : #{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}#{name} : #{address_hash} \n" end) end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 61d7465f49..bf490e4091 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -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 "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 5333eb95aa..107563ea66 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -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 "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index 1b9273c66c..4b43909ac4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -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 diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index defd5ae9e5..fed5124a2a 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -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" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 17116658bf..0e01212c37 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -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 diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 3b109504eb..78d162fa4e 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -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 = diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 233c15f8b3..1065ede2bd 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -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. diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex new file mode 100644 index 0000000000..e0864ac279 --- /dev/null +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -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 diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 9e9d70ae02..336eae106b 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -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 diff --git a/apps/explorer/lib/explorer/market/market_history_cache.ex b/apps/explorer/lib/explorer/market/market_history_cache.ex index 04b6193716..4ed3d9b7d2 100644 --- a/apps/explorer/lib/explorer/market/market_history_cache.ex +++ b/apps/explorer/lib/explorer/market/market_history_cache.ex @@ -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 diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 72b40f55a4..cc3aae5e83 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -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 diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 38e61dee7d..5ee506982b 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -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 diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 00050db6f4..490dce199d 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -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) diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs index ab63b74d1e..82f0a54385 100644 --- a/apps/explorer/test/explorer/smart_contract/reader_test.exs +++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs @@ -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 diff --git a/docker/Dockerfile b/docker/Dockerfile index 73501a9b9d..f73c4b34f0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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