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 @@
-
+
@@ -91,8 +91,8 @@
<%= gettext "Something went wrong, click to reload." %>
-
-
+
+
@@ -117,13 +117,7 @@
<%= gettext "Something went wrong, click to retry." %>
-
-
-
-
-
- <%= gettext("Loading...") %>
-
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
new file mode 100644
index 0000000000..ac7cc71f56
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
new file mode 100644
index 0000000000..134613a5d1
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
index 4c97be61e1..e819285629 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
@@ -23,13 +23,8 @@
<%= gettext "There are no pending transactions." %>
-
-
-
-
-
-
- <%= gettext("Loading") %>...
+
+ <%= 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/holder/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
index 3b24912dd5..8e83021c80 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/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/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