diff --git a/.circleci/config.yml b/.circleci/config.yml index f410162b48..b3abd48786 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0-node-browsers + - image: circleci/elixir:1.9.1-node-browsers environment: MIX_ENV: test # match POSTGRES_PASSWORD for postgres image below @@ -129,7 +129,7 @@ jobs: check_formatted: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test @@ -143,7 +143,7 @@ jobs: credo: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test @@ -177,7 +177,7 @@ jobs: dialyzer: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test @@ -247,7 +247,7 @@ jobs: gettext: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test @@ -286,7 +286,7 @@ jobs: release: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: prod @@ -312,7 +312,7 @@ jobs: sobelow: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test @@ -336,7 +336,7 @@ jobs: test_geth_http_websocket: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0-node-browsers + - image: circleci/elixir:1.9.1-node-browsers environment: MIX_ENV: test # match POSTGRES_PASSWORD for postgres image below @@ -390,7 +390,7 @@ jobs: test_geth_mox: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0-node-browsers + - image: circleci/elixir:1.9.1-node-browsers environment: MIX_ENV: test # match POSTGRES_PASSWORD for postgres image below @@ -444,7 +444,7 @@ jobs: test_parity_http_websocket: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0-node-browsers + - image: circleci/elixir:1.9.1-node-browsers environment: MIX_ENV: test # match POSTGRES_PASSWORD for postgres image below @@ -498,7 +498,7 @@ jobs: test_parity_mox: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0-node-browsers + - image: circleci/elixir:1.9.1-node-browsers environment: MIX_ENV: test # match POSTGRES_PASSWORD for postgres image below @@ -552,7 +552,7 @@ jobs: coveralls_merge: docker: # Ensure .tool-versions matches - - image: circleci/elixir:1.9.0 + - image: circleci/elixir:1.9.1 environment: MIX_ENV: test diff --git a/.credo.exs b/.credo.exs index ab3359abb1..ba4d7feb49 100644 --- a/.credo.exs +++ b/.credo.exs @@ -89,7 +89,7 @@ # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. # - {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, + {Credo.Check.Design.DuplicatedCode, excluded_macros: [], mass_threshold: 80}, # You can also customize the exit_status of each check. # If you don't want TODO comments to cause `mix credo` to fail, just diff --git a/.tool-versions b/.tool-versions index 71379b2fa7..785ba751f8 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.9 -erlang 21.0.4 +elixir 1.9.1-otp-22 +erlang 22.0 nodejs 10.11.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a5e56e6d..17adfb0acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,67 @@ ## Current ### Features +- [#2477](https://github.com/poanetwork/blockscout/pull/2477) - aggregate token transfers on transaction page +- [#2458](https://github.com/poanetwork/blockscout/pull/2458) - Add LAST_BLOCK var to add ability indexing in the range of blocks +- [#2456](https://github.com/poanetwork/blockscout/pull/2456) - fetch pending transactions for geth - [#2403](https://github.com/poanetwork/blockscout/pull/2403) - Return gasPrice field at the result of gettxinfo method + +### Fixes +- [#2520](https://github.com/poanetwork/blockscout/pull/2520) - Hide loading message when fetching is failed +- [#2523](https://github.com/poanetwork/blockscout/pull/2523) - Avoid importing internal_transactions of pending transactions +- [#2519](https://github.com/poanetwork/blockscout/pull/2519) - enable `First` page button in pagination +- [#2515](https://github.com/poanetwork/blockscout/pull/2515) - do not aggregate NFT token transfers +- [#2512](https://github.com/poanetwork/blockscout/pull/2512) - alert link fix +- [#2508](https://github.com/poanetwork/blockscout/pull/2508) - logs view columns fix +- [#2506](https://github.com/poanetwork/blockscout/pull/2506) - fix two active tab in the top menu +- [#2503](https://github.com/poanetwork/blockscout/pull/2503) - Mitigate autocompletion library influence to page loading performance +- [#2502](https://github.com/poanetwork/blockscout/pull/2502) - increase reward task timeout +- [#2463](https://github.com/poanetwork/blockscout/pull/2463) - dark theme fixes +- [#2496](https://github.com/poanetwork/blockscout/pull/2496) - fix docker build +- [#2495](https://github.com/poanetwork/blockscout/pull/2495) - fix logs for indexed chain +- [#2459](https://github.com/poanetwork/blockscout/pull/2459) - fix top addresses query +- [#2425](https://github.com/poanetwork/blockscout/pull/2425) - Force to show address view for checksummed address even if it is not in DB + +### Chore +- [#2507](https://github.com/poanetwork/blockscout/pull/2507) - update minor version of ecto, ex_machina, phoenix_live_reload +- [#2516](https://github.com/poanetwork/blockscout/pull/2516) - update absinthe plug from fork +- [#2473](https://github.com/poanetwork/blockscout/pull/2473) - get rid of cldr warnings +- [#2402](https://github.com/poanetwork/blockscout/pull/2402) - bump otp version to 22.0 +- [#2492](https://github.com/poanetwork/blockscout/pull/2492) - hide decoded row if event is not decoded +- [#2490](https://github.com/poanetwork/blockscout/pull/2490) - enable credo duplicated code check +- [#2432](https://github.com/poanetwork/blockscout/pull/2432) - bump credo version +- [#2457](https://github.com/poanetwork/blockscout/pull/2457) - update mix.lock +- [#2435](https://github.com/poanetwork/blockscout/pull/2435) - Replace deprecated extract-text-webpack-plugin with mini-css-extract-plugin +- [#2450](https://github.com/poanetwork/blockscout/pull/2450) - Fix clearance of logs and node_modules folders in clearing script +- [#2434](https://github.com/poanetwork/blockscout/pull/2434) - get rid of timex warnings +- [#2402](https://github.com/poanetwork/blockscout/pull/2402) - bump otp version to 22.0 +- [#2373](https://github.com/poanetwork/blockscout/pull/2373) - Add script to validate internal_transactions constraint for large DBs + +## 2.0.2-beta + +### Features +- [#2412](https://github.com/poanetwork/blockscout/pull/2412) - dark theme +- [#2399](https://github.com/poanetwork/blockscout/pull/2399) - decode verified smart contract's logs +- [#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 +- [#2421](https://github.com/poanetwork/blockscout/pull/2421) - Fix hiding of loader for txs on the main page +- [#2420](https://github.com/poanetwork/blockscout/pull/2420) - fetch data from cache in healthy endpoint +- [#2416](https://github.com/poanetwork/blockscout/pull/2416) - Fix "page not found" handling in the router +- [#2413](https://github.com/poanetwork/blockscout/pull/2413) - remove outer tables for decoded data +- [#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 +- [#2389](https://github.com/poanetwork/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used) +- [#2388](https://github.com/poanetwork/blockscout/pull/2388) - add create2 support to geth's js tracer +- [#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 - [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict @@ -25,6 +79,9 @@ - [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments ### Chore +- [#2422](https://github.com/poanetwork/blockscout/pull/2422) - check if address_id is binary in token_transfers_csv endpoint +- [#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/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 3ac2a5a1d1..4f889b7304 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -35,3 +35,5 @@ - [ ] If I added new functionality, I added tests covering it. - [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again. - [ ] I checked whether I should update the docs and did so if necessary + - [ ] If I added/changed/removed ENV var, I should update the list of env vars in https://github.com/poanetwork/blockscout/blob/master/docs/env-variables.md to reflect changes in the table here https://poanetwork.github.io/blockscout/#/env-variables?id=blockscout-env-variables. I've set `master` in the `Version` column. + - [ ] If I add new indices into DB, I checked, that they don't redundant with PGHero or other tools diff --git a/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js index 6e86d8a6b5..adae2d6033 100644 --- a/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js @@ -46,14 +46,12 @@ describe('REQUEST_ERROR', () => { describe('FINISH_REQUEST', () => { test('sets loading status to false', () => { const state = Object.assign({}, asyncInitialState, { - loading: true, - loadingFirstPage: true + loading: true }) const action = { type: 'FINISH_REQUEST' } const output = asyncReducer(state, action) expect(output.loading).toEqual(false) - expect(output.loadingFirstPage).toEqual(false) }) }) 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/app.scss b/apps/block_scout_web/assets/css/app.scss index cf79ee46e8..e91df91740 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -128,6 +128,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/new_smart_contract"; @import "components/radio_big"; @import "components/btn_no_border"; +@import "theme/dark-theme"; :export { dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color; @@ -135,4 +136,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; dashboardLineColorPrice: $dashboard-line-color-price; primary: $primary; secondary: $secondary; + darkprimary: $dark-primary; + darksecondary: $dark-secondary; } diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index 48c6c50f02..0d892b25ca 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -252,4 +252,10 @@ $navbar-logo-width: auto !default; @include media-breakpoint-up(xl) { padding-right: 0; } +} + +.nav-item-networks { + .topnav-nav-link { + transition: none !important; + } } \ No newline at end of file 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..e621e4fd6d 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -339,9 +339,9 @@ $tile-body-a-color: #5959d8 !default; padding-left: 6px; padding-right: 6px; } - .tile-type-block { - overflow: hidden; - } + } + .tile-type-block { + overflow: hidden; } .row { @include media-breakpoint-down(lg) { @@ -351,3 +351,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/css/theme/_base_variables.scss b/apps/block_scout_web/assets/css/theme/_base_variables.scss index 9f1b82c887..2238327bd4 100644 --- a/apps/block_scout_web/assets/css/theme/_base_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_base_variables.scss @@ -70,7 +70,10 @@ $colors: map-merge( ); $primary: $indigo !default; +$dark-primary: #9b62ff !default; +$dark-primary-alternate: #9b62ff !default; $secondary: #7dd79f !default; +$dark-secondary: #87e1a9 !default; $tertiary: $purple !default; $success: $green !default; $info: $cyan !default; diff --git a/apps/block_scout_web/assets/css/theme/_dai_variables.scss b/apps/block_scout_web/assets/css/theme/_dai_variables.scss index 50cbcfa290..0c09660240 100644 --- a/apps/block_scout_web/assets/css/theme/_dai_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_dai_variables.scss @@ -62,4 +62,9 @@ $card-tab-active: $secondary; // Badges $badge-neutral-color: #20446e; $badge-neutral-background-color: rgba(#20446e, .1); -$api-text-monospace-color: #20446e; \ No newline at end of file +$api-text-monospace-color: #20446e; + +// Dark theme +$dark-primary: #15bba6; +$dark-secondary: #93d7ff; +$dark-primary-alternate: #15bba6; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_dark-theme.scss b/apps/block_scout_web/assets/css/theme/_dark-theme.scss new file mode 100644 index 0000000000..fe807aea60 --- /dev/null +++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss @@ -0,0 +1,688 @@ +$body-dark: #1c1d31; // body background +$dark-bg: #22223a; // hero shade +$dark-light-bg: #282945; // pills bg shade +$dark-light: #313355; // tile light top share +$labels-dark: #8a8dba; // header nav, labels + +// Switcher +.dark-mode-changer { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background: transparent; + border: none; + cursor: pointer; + margin-right: 5px; + outline: none !important; + box-shadow: none !important; + transition: .2s ease-in; + &:hover { + opacity: .8; + } + svg path { + fill: #828ba0; + } + &--dark { + svg path { + fill: $dark-primary; + } + } +} + +.dark-theme-applied { + color: #fff; + + // navbar + .navbar.navbar-primary { + background-color: $dark-light-bg; + } + .navbar-brand .navbar-logo { + filter: brightness(0) invert(1); + } + .navbar.navbar-primary .navbar-nav .nav-link { + color: $labels-dark; + .nav-link-icon { + svg path { + fill: $labels-dark; + } + } + &.active, &:hover { + .nav-link-icon { + svg path { + fill: $dark-primary; + } + } + &:before { + background-color: $dark-primary; + } + } + } + .navbar.navbar-primary .form-control { + background-color: $dark-bg; + border-color: $dark-bg; + color: #fff; + &::placeholder { + color: $labels-dark; + } + } + .navbar.navbar-primary .navbar-toggler .navbar-toggler-icon { + filter: invert(1); + } + + // footer + .footer { + background: $dark-light-bg; + color: $labels-dark; + } + .footer-social-icon, + .footer-link { + color: $labels-dark; + } + .footer-social-icon:hover, + .footer-link:hover { + color: #fff; + } + .footer-list ul li::before { + background-color: $dark-secondary; + } + + // hero stats + + .layout-container .dashboard-banner-container { + background-image: none; + background-color: $dark-bg; + } + .dashboard-banner-network-plain-container, + .dashboard-banner-network-plain-container::after { + background-color: $dark-light-bg; + } + .dashboard-banner-network-stats-label, + .dashboard-banner-chart-legend .dashboard-banner-chart-legend-label { + color: $labels-dark; + } + .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before { + background-color: $dark-primary; + } + .dashboard-banner-network-stats-item::before { + background-color: $dark-secondary; + } + .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before { + background-color: $dark-secondary; + } + + // main container, layout, cards + .layout-container main { + background-color: $body-dark; + } + + .card { + background-color: $dark-light-bg; + box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5); + } + + .card-header { + border-bottom-color: darken($labels-dark, 30); + } + + .address-detail-hash-title { + color: #fff; + } + + .card-tabs { + border-bottom-color: darken($labels-dark, 30); + } + + .card-tab { + background-color: transparent; + &:hover:not(.active) { + background-color: rgba($dark-primary, .15); + color: $dark-primary; + } + &.active { + background-color: $dark-primary-alternate; + color: #fff; + } + } + + .card-background-1 { + background-color: $dark-primary-alternate; + } + + // Components + a { + color: $dark-primary; + } + + .tile { + border-top-color: $dark-light; + border-bottom-color: $dark-light; + border-right-color: $dark-light; + background-color: $dark-light; + color: $labels-dark; + &:not([class^="tile tile-type"]) { + border-left-color: $dark-light; + } + &.tile-type-coin-balance { + border-left-color: $dark-light; + } + .tile-title { + color: #fff; + } + .tile-transaction-type-block { + background-color: transparent; + } + } + .tile-bottom-contents { + background-color: $dark-bg; + } + a.tile-title { + color: #fff !important; + } + .tile.tile-type-block .tile-transaction-type-block a { + color: #fff; + } + .fade-up-blocks-chain .tile-type-block-animation { + background-color: $dark-light; + border-top-color: $dark-light; + border-right-color: $dark-light; + border-bottom-color: $dark-light; + } + .fade-up-blocks-chain .tile-type-block-animation:after { + background-color: $dark-light; + } + .cube-animation-title { + color: $labels-dark; + } + .tile .tile-body a, + .tile span[data-address-hash] { color: $dark-primary; } + .fade-up-blocks-chain .tile-type-block-animation .tile-type-line-up { + background-color: $dark-primary; + } + .tile.tile-type-block { + border-left-color: $dark-primary + } + .tile.tile-type-block .tile-status-label { + color: $dark-primary; + } + .tile.tile-type-block .tile-transaction-type-block { + border-right-color: $dark-primary; + border-top-color: $dark-primary; + border-bottom-color: $dark-primary; + } + .tile .progress { + background-color: rgba(#fff, .2); + } + .tile .progress .progress-bar { + background-color: $dark-primary; + } + .tile .tile-title-lg:not([data-balance-change-sign]) { + color: $dark-primary; + } + + // btns + + .btn-line { + background-color: transparent; + border-color: $dark-primary; + color: $dark-primary; + &:hover { + border-color: $dark-primary; + background-color: $dark-primary; + color: #fff; + } + } + + .btn-copy-icon, .btn-qr-icon { + border-color: $dark-primary; + path { + fill: $dark-primary; + } + &:hover { + background-color: $dark-primary; + path { + fill: #fff; + } + } + } + + // pagination + .pagination-container .pagination .page-link { + color: $labels-dark; + border-color: $dark-light; + background-color: $dark-light; + &:not(.no-hover):hover { + color: #fff; + background-color: $dark-primary; + path { + fill: #fff; + } + } + &[disabled] { + color: $labels-dark; + border-color: $dark-light; + background-color: $dark-light; + } + } + + // dropdown + .dropdown-menu { + background-color: $dark-light; + border-left-color: $dark-light; + border-right-color: $dark-light; + border-bottom-color: $dark-light; + } + + .dropdown-item { + color: #fff; + &:hover { + background-color: rgba(#fff, .1); + } + } + .dropdown-item.active { + background-color: $dark-primary; + } + .btn-dropdown-line { + background-color: $dark-light; + border-color: $dark-light; + color: $labels-dark; + } + + // table + .stakes-table-th { + background-color: $dark-light; + color: $labels-dark; + } + .stakes-td { + border-bottom-color: darken($labels-dark, 30); + } + .table th, .table td { + border-top-color: darken($labels-dark, 30); + } + hr { + border-top-color: darken($labels-dark, 30); + } + + // api's + .api-anchors-list { + background-color: $dark-light; + } + .api-doc-list-item { + border-bottom-color: darken($labels-dark, 30); + } + .card-subtitle, + .api-anchors-list-item-title, + .api-doc-list-item-title { + color: #fff; + } + .api-text-monospace { + color: $dark-primary; + } + .api-text-monospace-background { + background-color: rgba($dark-primary, .15); + } + .badge.badge-neutral { + background-color: rgba($dark-primary, .15); + color: $dark-primary; + } + + // download csv button + .download-all-transactions .download-all-transactions-link svg path { + fill: $dark-primary; + } + + //tooltips + .tooltip .arrow:before { + border-top-color: $dark-primary; + border-bottom-color: $dark-primary; + } + .tooltip > .tooltip-inner {background-color: $dark-primary;} + + //network select + .network-selector-overlay { + background-color: rgba($dark-bg, .9); + } + .network-selector { + background-color: $dark-light-bg; + } + .network-selector-title { + color: #fff; + } + .network-selector-text { + color: $labels-dark; + } + .network-selector-close path { + fill: #fff; + } + .network-selector-search-container { + background-color: $dark-light; + } + .network-selector-search-container path { + fill: $labels-dark; + } + .network-selector-search-input { + color: #fff !important; + &::placeholder { + color: $labels-dark; + } + } + .network-selector-tab { + color: $labels-dark; + &:hover, &.active { + color: #fff; + } + &.active { + &:after { + background-color: $dark-primary; + } + } + } + .network-selector-item, + .network-selector-tabs-container { + border-bottom-color: darken($labels-dark, 30); + } + .network-selector-item-title { + color: #fff; + } + .network-selector-item-type { + color: $labels-dark; + } + .radio .radio-icon { + border-color: $labels-dark + } + .network-selector-item-url:hover .network-selector-item-type { + color: #fff; + } + + //coin dropdown + .token-balance-dropdown.dropdown-menu { + border-color: $dark-light !important; + box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5) !important; + } + .token-balance-dropdown .dropdown-search-icon path { + fill: $labels-dark; + } + .token-balance-dropdown .dropdown-search-field { + background-color: $dark-light; + border-color: $dark-light; + color: #fff; + &::placeholder { + color: $labels-dark; + } + } + .token-balance-dropdown[aria-labelledby="dropdown-tokens"] .dropdown-items .dropdown-item:hover { + color: #fff !important; + } + .dropdown-header { + color: $labels-dark; + } + .border-bottom { + border-bottom-color: darken($labels-dark, 30) !important; + } + + // coin balance history chart + .chartjs-render-monitor[data-chart="coinBalanceHistoryChart"] { + filter: brightness(0) invert(1) !important; + } + + // logs search + .logs-search-input, .logs-search-btn, .logs-search-btn-cancel { + background-color: $dark-light; + border-color: $dark-light; + color: #fff; + } + + .logs-search-btn { + color: $labels-dark; + } + + .logs-search-btn { + &:hover { + background-color: $dark-primary; + color: #fff; + } + } + + .logs-search-input { + &::placeholder { + color: $labels-dark; + } + } + + // code + pre { + color: #fff; + } + + // info allert + .alert-info { + color: $labels-dark; + background-color: $dark-light; + border-color: $dark-light; + } + + // dark text + .text-dark { + color: #fff !important; + } + + // Contract Verification + .new-smart-contract-container { + background-color: $dark-light-bg; + background-image: linear-gradient(to bottom right, $dark-light 100%, $dark-light 100%); + @media (max-width: 991.98px) { + background-image: none; + } + } + .smart-contract-form-group-inner-wrapper .smart-contract-form-group-tooltip { + color: $labels-dark; + } + .smart-contract-title { + color: #fff; + } + .smart-contract-form-group-inner-wrapper > label { + color: $labels-dark; + } + .smart-contract-form-buttons { + border-top-color: darken($labels-dark, 30); + .btn-no-border { + background-color: $dark-light; + border-color: $dark-light; + color: #fff; + &:hover { + background-color: $dark-primary; + color: #fff; + } + } + } + .add-contract-libraries-wrapper { + border-top-color: darken($labels-dark, 30); + } + + .token-tile-view-more:before, .token-tile-view-more:after { + border-top-color: darken($labels-dark, 30); + border-bottom-color: darken($labels-dark, 30); + } + + // Form Controlls + .form-control { + background-color: $dark-light; + border-color: $dark-light; + color: #fff; + &::placeholder { + color: $labels-dark; + } + &:focus { + background-color: $dark-light; + border-color: $dark-primary; + color: #fff; + } + } + .radio-big .radio-icon { + border-color: $labels-dark; + } + .radio-big input[type="radio"]:checked + .radio-icon { + border-color: $dark-primary; + } + .radio-big input[type="radio"]:checked + .radio-icon::before { + background: $dark-primary; + } + .radio-big .radio-text { + color: #fff; + } + + // Content loading placeholders + .tile-loader, .table-content-loader { + background-color: $dark-bg !important; + &:before { + background: linear-gradient(to right, $dark-bg 2%, lighten($dark-bg, 3) 18%, $dark-bg 33%); + } + } + + // Verify other explorers + .verify-other-explorers-elem { + border-color: darken($labels-dark, 30); + .exp-logo { + border-right-color: darken($labels-dark, 30); + } + .exp-content { + h3 { + color: #fff; + } + div { + color: $labels-dark; + } + } + } + .verify-other-explorers-more { + border-color: $dark-primary; + svg path { + fill: $dark-primary; + } + &:hover { + background-color: $dark-primary; + svg path { + fill: #fff; + } + } + } + .verify-other-explorers-elem { + &:hover { + text-decoration: none; + color: #fff; + .exp-content { + h3, div { + color: #fff; + } + } + } + } + .verify-other-explorers-cell { + .exp-logo { + color: #333 !important; + } + } + + // API docs dropdown content + .api-doc-parameters-list-item-description, + .api-doc-parameters-list-item-title, + .api-doc-parameters-list-title, + .api-doc-list-item-controls-view-more { + color: #fff; + } + + .api-doc-parameters-list { + border-bottom-color: darken($labels-dark, 30); + } + .api-doc-parameters-container { + border-top-color: darken($labels-dark, 30); + } + .api-doc-tab { + color: $dark-primary !important; + &.active { + border-bottom-color: $dark-primary; + } + } + + // Common Buttons + .btn-secondary, .button-secondary { + background-color: transparent; + border-color: $dark-primary; + color: $dark-primary; + &:hover { + background-color: $dark-primary; + border-color: $dark-primary; + color: #fff; + } + } + + .awesomplete { + & > ul { + background: $dark-light-bg; + &:before { + background: $dark-light-bg; + } + li { + &:hover { + background-color: $dark-primary; + color: #fff; + mark { + background: darken($dark-primary, 10); + color: #fff; + } + } + } + } + mark { + background: $dark-primary; + color: #fff; + } + } + + // Decoded data + .table.thead-light.table-bordered { + color: #fff !important; + } + .table-bordered, .table-bordered td, .table-bordered th { + border-color: darken($labels-dark, 30); + } + .dark-theme-applied .table td, .dark-theme-applied .table th, .dark-theme-applied hr { + border-top-color: darken($labels-dark, 30); + } + .btn-copy-ico svg path { + fill: #fff; + } + + // pre + .pre-scrollable.line-numbers, .hljs { + background: $dark-light; + color: #fff; + } + + .hljs-comment { + color: $labels-dark; + } + + .hljs-title, .hljs-section { + color: #ff2294; + } + + .hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion { + color: #ff2294; + } + + .hljs-literal, .hljs-built_in, .hljs-bullet, .hljs-code, .hljs-addition { + color: #20dd94; + } + + .line-numbers [data-line-number]:before { + color: #3f436b !important; + border-right-color: #3f436b !important; + } + + // alert link + .alert-link { + color: $dark-secondary; + } +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss b/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss index 68feab4dfc..dff677d7cc 100644 --- a/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss @@ -74,4 +74,9 @@ $card-tab-active: $tertiary; // Badges $badge-neutral-color: $tertiary; $badge-neutral-background-color: rgba($tertiary, .1); -$api-text-monospace-color: $tertiary; \ No newline at end of file +$api-text-monospace-color: $tertiary; + +// Dark theme +$dark-primary: #8588ff; +$dark-secondary: #4ad7a7; +$dark-primary-alternate: #5b5ed8; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss index 0755d3e4b4..0b5cac0c43 100644 --- a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss @@ -56,4 +56,18 @@ $card-tab-active: $secondary; $dashboard-banner-gradient-end ); } +} + +// Dark theme +$dark-primary: #49a2ee; +$dark-secondary: #4ad7a7; +$dark-primary-alternate: #49a2ee; + +.dark-theme-applied { + .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before { + background-color: $dark-primary !important; + } + .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before { + background-color: $dark-secondary !important; + } } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_goerli_variables.scss b/apps/block_scout_web/assets/css/theme/_goerli_variables.scss index 7900dd4c3b..04f953b477 100644 --- a/apps/block_scout_web/assets/css/theme/_goerli_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_goerli_variables.scss @@ -78,3 +78,8 @@ $card-tab-active: $sub-accent-color; $badge-neutral-color: $sub-accent-color; $badge-neutral-background-color: rgba($sub-accent-color, .1); $api-text-monospace-color: $sub-accent-color; + +// Dark theme +$dark-primary: #e1995a; +$dark-secondary: #aeaeae; +$dark-primary-alternate: #e1995a; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_kovan_variables.scss b/apps/block_scout_web/assets/css/theme/_kovan_variables.scss index 351f046726..a4edc629d0 100644 --- a/apps/block_scout_web/assets/css/theme/_kovan_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_kovan_variables.scss @@ -74,4 +74,9 @@ $badge-success-color: #15bba6; $badge-success-background-color: rgba(#15bba6, .1); $badge-neutral-color: $tertiary; $badge-neutral-background-color: rgba($tertiary, .1); -$api-text-monospace-color: $tertiary; \ No newline at end of file +$api-text-monospace-color: $tertiary; + +// Dark theme +$dark-primary: #42e2d7; +$dark-secondary: #1f857f; +$dark-primary-alternate: #1f857f; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss index 65f9a73b5f..a7b608bfa8 100644 --- a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss @@ -151,3 +151,20 @@ $dashboard-banner-network-plain-container-height: 150px; $badge-neutral-color: $tertiary; $badge-neutral-background-color: rgba($tertiary, .1); $api-text-monospace-color: $tertiary; + +// Dark theme +$dark-primary: #fdcec4; +$dark-secondary: #a96c55; +$dark-primary-alternate: #a96c55; + +.dark-theme-applied { + .dashboard-banner-network-stats-value { + color: $dark-primary !important; + } + .layout-container .dashboard-banner-container { + background-color: #282945 !important; + } + .dashboard-banner-network-plain-container::after { + box-shadow: none !important; + } +} diff --git a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss index 4033934dd6..120a11c222 100644 --- a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss @@ -71,4 +71,9 @@ $api-text-monospace-color: $primary; .dropdown-items .dropdown-item:hover { color: $primary !important; } -} \ No newline at end of file +} + +// Dark theme +$dark-primary: #9b62ff; +$dark-secondary: #87e1a9; +$dark-primary-alternate: #7e50d0; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_poa_variables.scss b/apps/block_scout_web/assets/css/theme/_poa_variables.scss index 4033934dd6..120a11c222 100644 --- a/apps/block_scout_web/assets/css/theme/_poa_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_poa_variables.scss @@ -71,4 +71,9 @@ $api-text-monospace-color: $primary; .dropdown-items .dropdown-item:hover { color: $primary !important; } -} \ No newline at end of file +} + +// Dark theme +$dark-primary: #9b62ff; +$dark-secondary: #87e1a9; +$dark-primary-alternate: #7e50d0; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss index 1e62ea6d69..4fa700c50e 100644 --- a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss @@ -57,3 +57,8 @@ $card-tab-active: $secondary; ); } } + +// Dark theme +$dark-primary: #38a9f5; +$dark-secondary: #76f1ff; +$dark-primary-alternate: #38a9f5; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss b/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss index 1e62ea6d69..4fa700c50e 100644 --- a/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss @@ -57,3 +57,8 @@ $card-tab-active: $secondary; ); } } + +// Dark theme +$dark-primary: #38a9f5; +$dark-secondary: #76f1ff; +$dark-primary-alternate: #38a9f5; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss index 76029e1a34..d33996a89e 100644 --- a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss @@ -65,3 +65,8 @@ $card-tab-active: $secondary; // Badges $badge-neutral-color: #1a323b; $badge-neutral-background-color: rgba(#1a323b, .1); + +// Dark theme +$dark-primary: #38c5a4; +$dark-secondary: #e39a54; +$dark-primary-alternate: #30ab8d; diff --git a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss index 444cdff178..8d479f397d 100644 --- a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss @@ -72,4 +72,9 @@ $card-tab-active: $sub-accent-color; // Badges $badge-neutral-color: $tertiary; -$badge-neutral-background-color: rgba($tertiary, .1); \ No newline at end of file +$badge-neutral-background-color: rgba($tertiary, .1); + +// Dark theme +$dark-primary: #40bfb2; +$dark-secondary: #25c9ff; +$dark-primary-alternate: #1c9f90; \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index 25de7b2688..89059dd306 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -35,6 +35,7 @@ import './pages/favorites' import './pages/network-search' import './pages/layout' import './pages/verification_form' +import './pages/dark-mode-switcher' import './pages/admin/tasks.js' 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..1c1fec4baa 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' @@ -50,8 +51,6 @@ export const asyncInitialState = { requestError: false, /* if response has no items */ emptyResponse: false, - /* if it is loading the first page */ - loadingFirstPage: true, /* link to the next page */ nextPagePath: null, /* link to the previous page */ @@ -79,8 +78,7 @@ export function asyncReducer (state = asyncInitialState, action) { } case 'FINISH_REQUEST': { return Object.assign({}, state, { - loading: false, - loadingFirstPage: false + loading: false }) } case 'ITEMS_FETCHED': { @@ -133,7 +131,7 @@ export const elements = { }, '[data-async-listing] [data-loading-message]': { render ($el, state) { - if (state.loadingFirstPage) return $el.show() + if (state.loading) return $el.show() $el.hide() } @@ -142,7 +140,7 @@ export const elements = { render ($el, state) { if ( !state.requestError && - (!state.loading || !state.loadingFirstPage) && + (!state.loading) && state.items.length === 0 ) { return $el.show() @@ -164,7 +162,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 } @@ -200,6 +198,16 @@ export const elements = { $el.attr('href', state.prevPagePath) } }, + '[data-async-listing] [data-first-page-button]': { + render ($el, state) { + if (state.pagesStack.length === 0) { + return $el.hide() + } + $el.show() + $el.attr('disabled', false) + $el.attr('href', window.location.href.split('?')[0]) + } + }, '[data-async-listing] [data-page-number]': { render ($el, state) { if (state.emptyResponse) { @@ -215,7 +223,7 @@ export const elements = { }, '[data-async-listing] [data-loading-button]': { render ($el, state) { - if (!state.loadingFirstPage && state.loading) return $el.show() + if (state.loading) return $el.show() $el.hide() } @@ -244,7 +252,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/awesomplete-util.js b/apps/block_scout_web/assets/js/lib/awesomplete-util.js new file mode 100644 index 0000000000..5b37e1dd2f --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/awesomplete-util.js @@ -0,0 +1,637 @@ +/* eslint-env browser */ +/* global Awesomplete */ +/* exported AwesompleteUtil */ + +/* + * Library endorsing Lea Verou's Awesomplete widget, providing: + * - dynamic remote data loading + * - labels with HTML markup + * - events and styling for exact matches + * - events and styling for mismatches + * - select item when TAB key is used + * + * (c) Nico Hoogervorst + * License: MIT + * + */ +window.AwesompleteUtil = (function () { + // + // event names and css classes + // + var _AWE = 'awesomplete-' + var _AWE_LOAD = _AWE + 'loadcomplete' + var _AWE_CLOSE = _AWE + 'close' + var _AWE_MATCH = _AWE + 'match' + var _AWE_PREPOP = _AWE + 'prepop' + var _AWE_SELECT = _AWE + 'select' + var _CLS_FOUND = 'awe-found' + var _CLS_NOT_FOUND = 'awe-not-found' + var $ = Awesomplete.$ /* shortcut for document.querySelector */ + + // + // private functions + // + + // Some parts are shamelessly copied from Awesomplete.js like the logic inside this _suggestion function. + // Returns an object with label and value properties. Data parameter is plain text or Object/Array with label and value. + function _suggestion (data) { + var lv = Array.isArray(data) + ? { label: data[0], value: data[1] } + : typeof data === 'object' && 'label' in data && 'value' in data ? data : { label: data, value: data } + return {label: lv.label || lv.value, value: lv.value} + } + + // Helper to send events with detail property. + function _fire (target, name, detail) { + // $.fire uses deprecated methods but other methods don't work in IE11. + return $.fire(target, name, {detail: detail}) + } + + // Look if there is an exact match or a mismatch, set awe-found, awe-not-found css class and send match events. + function _matchValue (awe, prepop) { + var input = awe.input /* the input field */ + var classList = input.classList + var utilprops = awe.utilprops /* extra properties piggybacked on Awesomplete object */ + var selected = utilprops.selected /* the exact selected Suggestion with label and value */ + var val = utilprops.convertInput.call(awe, input.value) /* trimmed lowercased value */ + var opened = awe.opened /* is the suggestion list opened? */ + var result = [] /* matches with value */ + var list = awe._list /* current list of suggestions */ + var suggestion, fake, rec, j /* function scoped variables */ + utilprops.prepop = false /* after the first call it's not a prepopulation phase anymore */ + if (list) { /* if there is a suggestion list */ + for (j = 0; j < list.length; j++) { /* loop all suggestions */ + rec = list[j] + suggestion = _suggestion(awe.data(rec, val)) /* call data convert function */ + // with maxItems = 0 cannot look if suggestion list is opened to determine if there are still matches, + // instead call the filter method to see if there are still some options. + if (awe.maxItems === 0) { + // Awesomplete.FILTER_CONTAINS and Awesomplete.FILTER_STARTSWITH use the toString method. + suggestion.toString = function () { return '' + this.label } + if (awe.filter(suggestion, val)) { + // filter returns true, so there is at least one partial match. + opened = true + } + } + // Don't want to change the real input field, emulate a fake one. + fake = {input: {value: ''}} + // Determine how this suggestion would look like if it is replaced in the input field, + // it is an exact match if somebody types exactly that. + // Use the fake input here. fake.input.value will contain the result of the replace function. + awe.replace.call(fake, suggestion) + // Trim and lowercase also the fake input and compare that with the currently typed-in value. + if (utilprops.convertInput.call(awe, fake.input.value) === val) { + // This is an exact match. However there might more suggestions with the same value. + // If the user selected a suggestion from the list, check if this one matches, assuming that + // value + label is unique (if not it will be difficult for the user to make an informed decision). + if (selected && selected.value === suggestion.value && selected.label === suggestion.label) { + // this surely is the selected one + result = [rec] + break + } + // add the matching record to the result set. + result.push(rec) + } // end if + } // end loop + + // if the result differs from the previous result + if (utilprops.prevSelected !== result) { + // if there is an exact match + if (result.length > 0) { + // if prepopulation phase (initial/autofill value); not triggered by user input + if (prepop) { + _fire(input, _AWE_PREPOP, result) + } else if (utilprops.changed) { /* if input is changed */ + utilprops.prevSelected = result /* new result */ + classList.remove(_CLS_NOT_FOUND) /* remove class */ + classList.add(_CLS_FOUND) /* add css class */ + _fire(input, _AWE_MATCH, result) /* fire event */ + } + } else if (prepop) { /* no exact match, if in prepopulation phase */ + _fire(input, _AWE_PREPOP, []) + } else if (utilprops.changed) { /* no exact match, if input is changed */ + utilprops.prevSelected = [] + classList.remove(_CLS_FOUND) + // Mark as not-found if there are no suggestions anymore or if another field is now active + if (!opened || (input !== document.activeElement)) { + if (val.length > 0) { + classList.add(_CLS_NOT_FOUND) + _fire(input, _AWE_MATCH, []) + } + } else { + classList.remove(_CLS_NOT_FOUND) + } + } + } + } + } + + // Listen to certain events of THIS awesomplete object to trigger input validation. + function _match (ev) { + var awe = this + if ((ev.type === _AWE_CLOSE || ev.type === _AWE_LOAD || ev.type === 'blur') && ev.target === awe.input) { + _matchValue(awe, awe.utilprops.prepop && ev.type === _AWE_LOAD) + } + } + + // Select currently selected item if tab or shift-tab key is used. + function _onKeydown (ev) { + var awe = this + if (ev.target === awe.input && ev.keyCode === 9) { // TAB key + awe.select() // take current selected item + } + } + + // Handle selection event. State changes when an item is selected. + function _select (ev) { + var awe = this + awe.utilprops.changed = true // yes, user made a change + awe.utilprops.selected = ev.text // Suggestion object + } + + // check if the object is empty {} object + function _isEmpty (val) { + return Object.keys(val).length === 0 && val.constructor === Object + } + + // Need an updated suggestion list if: + // - There is no result yet, or there is a result but not for the characters we entered + // - or there might be more specific results because the limit was reached. + function _ifNeedListUpdate (awe, val, queryVal) { + var utilprops = awe.utilprops + return (!utilprops.listQuery || + (!utilprops.loadall && /* with loadall, if there is a result, there is no need for new lists */ + val.lastIndexOf(queryVal, 0) === 0 && + (val.lastIndexOf(utilprops.listQuery, 0) !== 0 || + (typeof utilprops.limit === 'number' && awe._list.length >= utilprops.limit)))) + } + + // Set a new suggestion list. Trigger loadcomplete event. + function _loadComplete (awe, list, queryVal) { + awe.list = list + awe.utilprops.listQuery = queryVal + _fire(awe.input, _AWE_LOAD, queryVal) + } + + // Handle ajax response. Expects HTTP OK (200) response with JSON object with suggestion(s) (array). + function _onLoad () { + var t = this + var awe = t.awe + var xhr = t.xhr + var queryVal = t.queryVal + var val = awe.utilprops.val + var data + var prop + if (xhr.status === 200) { + data = JSON.parse(xhr.responseText) + if (awe.utilprops.convertResponse) data = awe.utilprops.convertResponse(data) + if (!Array.isArray(data)) { + if (awe.utilprops.limit === 0 || awe.utilprops.limit === 1) { + // if there is max 1 result expected, the array is not needed. + // Fur further processing, take the whole result and put it as one element in an array. + data = _isEmpty(data) ? [] : [data] + } else { + // search for the first property that contains an array + for (prop in data) { + if (Array.isArray(data[prop])) { + data = data[prop] + break + } + } + } + } + // can only handle arrays + if (Array.isArray(data)) { + // are we still interested in this response? + if (_ifNeedListUpdate(awe, val, queryVal)) { + // accept the new suggestion list + _loadComplete(awe, data, queryVal || awe.utilprops.loadall) + } + } + } + } + + // Perform suggestion list lookup for the current value and validate. Use ajax when there is an url specified. + function _lookup (awe, val) { + var xhr + if (awe.utilprops.url) { + // are we still interested in this response? + if (_ifNeedListUpdate(awe, val, val)) { + xhr = new XMLHttpRequest() + awe.utilprops.ajax.call(awe, + awe.utilprops.url, + awe.utilprops.urlEnd, + awe.utilprops.loadall ? '' : val, + _onLoad.bind({awe: awe, xhr: xhr, queryVal: val}), + xhr + ) + } else { + _matchValue(awe, awe.utilprops.prepop) + } + } else { + _matchValue(awe, awe.utilprops.prepop) + } + } + + // Restart autocomplete search: clear css classes and send match-event with empty list. + function _restart (awe) { + var elem = awe.input + var classList = elem.classList + // IE11 only handles the first parameter of the remove method. + classList.remove(_CLS_NOT_FOUND) + classList.remove(_CLS_FOUND) + _fire(elem, _AWE_MATCH, []) + } + + // handle new input value + function _update (awe, val, prepop) { + // prepop parameter is optional. Default value is false. + awe.utilprops.prepop = prepop || false + // if value changed + if (awe.utilprops.val !== val) { + // new value, clear previous selection + awe.utilprops.selected = null + // yes, user made a change + awe.utilprops.changed = true + awe.utilprops.val = val + // value is empty or smaller than minChars + if (val.length < awe.minChars || val.length === 0) { + // restart autocomplete search + _restart(awe) + } + if (val.length >= awe.minChars) { + // lookup suggestions and validate input + _lookup(awe, val) + } + } + return awe + } + + // handle input changed event for THIS awesomplete object + function _onInput (e) { + var awe = this + var val + if (e.target === awe.input) { + // lowercase and trim input value + val = awe.utilprops.convertInput.call(awe, awe.input.value) + _update(awe, val) + } + } + + // item function (as specified in Awesomplete) which just creates the 'li' HTML tag. + function _item (html /* , input */) { + return $.create('li', { + innerHTML: html, + 'aria-selected': 'false' + }) + } + + // Escape HTML characters in text. + function _htmlEscape (text) { + return text.replace('&', '&').replace('<', '<').replace('>', '>') + } + + // Function to copy a field from the selected autocomplete item to another DOM element. + function _copyFun (e) { + var t = this + var sourceId = t.sourceId + var dataField = t.dataField + var targetId = t.targetId + var elem + var val + if (e.target === $(sourceId)) { + if (typeof targetId === 'function') { + targetId(e, dataField) + } else { + // lookup target element if it isn't resolved yet + elem = $(targetId) + // don't override target inputs if user is currently editing it. + if (elem && elem !== document.activeElement) { + // event must contain 1 item from suggestion list + val = Array.isArray(e.detail) && e.detail.length === 1 ? e.detail[0] : null + // if a datafield is specified, take that value + val = (dataField && val ? val[dataField] : val) || '' + // if it is an input control + if (typeof elem.value !== 'undefined') { + // set new value + elem.value = val + // not really sure if it is an input control, check if it has a classList + if (elem.classList && elem.classList.remove) { + // it might be another awesomplete control, if so the input is not wrong anymore because it's changed now + elem.classList.remove(_CLS_NOT_FOUND) + } + } else if (typeof elem.src !== 'undefined') { /* is it an image tag? */ + elem.src = val + } else { + // use innerHTML to set the new value, because value might intentionally contain HTML markup + elem.innerHTML = val + } + } + } + } + } + + // click function for the combobox button + function _clickFun (e) { + var t = this + var awe + var minChars + if (e.target === $(t.btnId)) { + e.preventDefault() + awe = t.awe + // toggle open/close + if (awe.ul.childNodes.length === 0 || awe.ul.hasAttribute('hidden')) { + minChars = awe.minChars + // ignore that the input value is empty + awe.minChars = 0 + // show the suggestion list + awe.evaluate() + awe.minChars = minChars + } else { + awe.close() + } + } + } + + // Return text with mark tags arround matching input. Don't replace inside tags. + // When startsWith is true, mark only the matching begin text. + function _mark (text, input, startsWith) { + var searchText = $.regExpEscape(_htmlEscape(input).trim()) + var regExp = searchText.length <= 0 ? null : startsWith ? RegExp('^' + searchText, 'i') : RegExp('(?!<[^>]+?>)' + searchText + '(?![^<]*?>)', 'gi') + return text.replace(regExp, '$&') + } + + // Recursive jsonFlatten function + function _jsonFlatten (result, cur, prop, level, opts) { + var root = opts.root /* filter resulting json tree on root property (optional) */ + var value = opts.value /* search for this property and copy it's value to a new 'value' property + (optional, do not specify it if the json array contains plain strings) */ + var label = opts.label || opts.value /* search this property and copy it's value to a new 'label' property. + If there is a 'opts.value' field but no 'opts.label', assume label is the same. */ + var isEmpty = true + var arrayResult = [] + var j + // at top level, look if there is a property which starts with root (if specified) + if (level === 0 && root && prop && (prop + '.').lastIndexOf(root + '.', 0) !== 0 && (root + '.').lastIndexOf(prop + '.', 0) !== 0) { + return result + } + // handle current part of the json tree + if (Object(cur) !== cur) { + if (prop) { + result[prop] = cur + } else { + result = cur + } + } else if (Array.isArray(cur)) { + for (j = 0; j < cur.length; j++) { + arrayResult.push(_jsonFlatten({}, cur[j], '', level + 1, opts)) + } + if (prop) { + result[prop] = arrayResult + } else { + result = arrayResult + } + } else { + for (j in cur) { + isEmpty = false + _jsonFlatten(result, cur[j], prop ? prop + '.' + j : j, level, opts) + } + if (isEmpty && prop) result[prop] = {} + } + // for arrays at top and subtop level + if (level < 2 && prop) { + // if a 'value' is specified and found a mathing property, create extra 'value' property. + if (value && (prop + '.').lastIndexOf(value + '.', 0) === 0) { result['value'] = result[prop] } + // if a 'label' is specified and found a mathing property, create extra 'label' property. + if (label && (prop + '.').lastIndexOf(label + '.', 0) === 0) { result['label'] = result[prop] } + } + if (level === 0) { + // Make sure that both value and label properties exist, even if they are nil. + // This is handy with limit 0 or 1 when the result doesn't have to contain an array. + if (value && !('value' in result)) { result['value'] = null } + if (label && !('label' in result)) { result['label'] = null } + } + return result + } + + // Stop AwesompleteUtil; detach event handlers from the Awesomplete object. + function _detach () { + var t = this + var elem = t.awe.input + var boundMatch = t.boundMatch + var boundOnInput = t.boundOnInput + var boundOnKeydown = t.boundOnKeydown + var boundSelect = t.boundSelect + + elem.removeEventListener(_AWE_SELECT, boundSelect) + elem.removeEventListener(_AWE_LOAD, boundMatch) + elem.removeEventListener(_AWE_CLOSE, boundMatch) + elem.removeEventListener('blur', boundMatch) + elem.removeEventListener('input', boundOnInput) + elem.removeEventListener('keydown', boundOnKeydown) + } + + // + // public methods + // + + return { + + // ajax call for url + val + urlEnd. fn is the callback function. xhr parameter is optional. + ajax: function (url, urlEnd, val, fn, xhr) { + xhr = xhr || new XMLHttpRequest() + xhr.open('GET', url + encodeURIComponent(val) + (urlEnd || '')) + xhr.onload = fn + xhr.send() + return xhr + }, + + // Convert input before comparing it with suggestion. lowercase and trim the text + convertInput: function (text) { + return typeof text === 'string' ? text.trim().toLowerCase() : '' + }, + + // item function as defined in Awesomplete. + // item(html, input). input is optional and ignored in this implementation + item: _item, + + // Set a new suggestion list. Trigger loadcomplete event. + // load(awesomplete, list, queryVal) + load: _loadComplete, + + // Return text with mark tags arround matching input. Don't replace inside tags. + // When startsWith is true, mark only the matching begin text. + // mark(text, input, startsWith) + mark: _mark, + + // highlight items: Marks input in the first line, not in the optional description + itemContains: function (text, input) { + var arr + if (input.trim().length > 0) { + arr = ('' + text).split(/

/) + arr[0] = _mark(arr[0], input) + text = arr.join('

') + } + return _item(text, input) + }, + + // highlight items: mark all occurrences of the input text + itemMarkAll: function (text, input) { + return _item(input.trim() === '' ? '' + text : _mark('' + text, input), input) + }, + + // highlight items: mark input in the begin text + itemStartsWith: function (text, input) { + return _item(input.trim() === '' ? '' + text : _mark('' + text, input, true), input) + }, + + // create Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete. + create: function (elemId, utilOpts, opts) { + opts.item = opts.item || this.itemContains /* by default uses itemContains, can be overriden */ + var awe = new Awesomplete(elemId, opts) + awe.utilprops = utilOpts || {} + // loadall is true if there is no url (there is a static data-list) + if (!awe.utilprops.url && typeof awe.utilprops.loadall === 'undefined') { + awe.utilprops.loadall = true + } + awe.utilprops.ajax = awe.utilprops.ajax || this.ajax /* default ajax function can be overriden */ + awe.utilprops.convertInput = awe.utilprops.convertInput || this.convertInput /* the same applies for convertInput */ + return awe + }, + + // attach Awesomplete object to event listeners + attach: function (awe) { + var elem = awe.input + var boundMatch = _match.bind(awe) + var boundOnKeydown = _onKeydown.bind(awe) + var boundOnInput = _onInput.bind(awe) + var boundSelect = _select.bind(awe) + var boundDetach = _detach.bind({awe: awe, + boundMatch: boundMatch, + boundOnInput: boundOnInput, + boundOnKeydown: boundOnKeydown, + boundSelect: boundSelect + }) + var events = { + 'keydown': boundOnKeydown, + 'input': boundOnInput + } + events['blur'] = events[_AWE_CLOSE] = events[_AWE_LOAD] = boundMatch + events[_AWE_SELECT] = boundSelect + $.bind(elem, events) + + awe.utilprops.detach = boundDetach + // Perform ajax call if prepop is true and there is an initial input value, or when all values must be loaded (loadall) + if (awe.utilprops.prepop && (awe.utilprops.loadall || elem.value.length > 0)) { + awe.utilprops.val = awe.utilprops.convertInput.call(awe, elem.value) + _lookup(awe, awe.utilprops.val) + } + return awe + }, + + // update input value via javascript. Use prepop=true when this is an initial/prepopulation value. + update: function (awe, value, prepop) { + awe.input.value = value + return _update(awe, value, prepop) + }, + + // create and attach Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete. + start: function (elemId, utilOpts, opts) { + return this.attach(this.create(elemId, utilOpts, opts)) + }, + + // Stop AwesompleteUtil; detach event handlers from the Awesomplete object. + detach: function (awe) { + if (awe.utilprops.detach) { + awe.utilprops.detach() + delete awe.utilprops.detach + } + return awe + }, + + // Create function to copy a field from the selected autocomplete item to another DOM element. + // dataField can be null. + createCopyFun: function (sourceId, dataField, targetId) { + return _copyFun.bind({sourceId: sourceId, dataField: dataField, targetId: $(targetId) || targetId}) + }, + + // attach copy function to event listeners. prepop is optional and by default true. + // if true the copy function will also listen to awesomplete-prepop events. + // The optional listenEl is the element that listens, defaults to document.body. + attachCopyFun: function (fun, prepop, listenEl) { + // prepop parameter defaults to true + prepop = typeof prepop === 'boolean' ? prepop : true + listenEl = listenEl || document.body + listenEl.addEventListener(_AWE_MATCH, fun) + if (prepop) listenEl.addEventListener(_AWE_PREPOP, fun) + return fun + }, + + // Create and attach copy function. + startCopy: function (sourceId, dataField, targetId, prepop) { + var sourceEl = $(sourceId) + return this.attachCopyFun(this.createCopyFun(sourceEl || sourceId, dataField, targetId), prepop, sourceEl) + }, + + // Stop copy function. Detach it from event listeners. + // The optional listenEl must be the same element that was used during startCopy/attachCopyFun; + // in general: Awesomplete.$(sourceId). listenEl defaults to document.body. + detachCopyFun: function (fun, listenEl) { + listenEl = listenEl || document.body + listenEl.removeEventListener(_AWE_PREPOP, fun) + listenEl.removeEventListener(_AWE_MATCH, fun) + return fun + }, + + // Create function for combobox button (btnId) to toggle dropdown list. + createClickFun: function (btnId, awe) { + return _clickFun.bind({btnId: btnId, awe: awe}) + }, + + // Attach click function for combobox to click event. + // The optional listenEl is the element that listens, defaults to document.body. + attachClickFun: function (fun, listenEl) { + listenEl = listenEl || document.body + listenEl.addEventListener('click', fun) + return fun + }, + + // Create and attach click function for combobox button. Toggles open/close of suggestion list. + startClick: function (btnId, awe) { + var btnEl = $(btnId) + return this.attachClickFun(this.createClickFun(btnEl || btnId, awe), btnEl) + }, + + // Stop click function. Detach it from event listeners. + // The optional listenEl must be the same element that was used during startClick/attachClickFun; + // in general: Awesomplete.$(btnId). listenEl defaults to document.body. + detachClickFun: function (fun, listenEl) { + listenEl = listenEl || document.body + listenEl.removeEventListener('click', fun) + return fun + }, + + // filter function as specified in Awesomplete. Filters suggestion list on items containing input value. + // Awesomplete.FILTER_CONTAINS filters on data.label, however + // this function filters on value and not on the shown label which may contain markup. + filterContains: function (data, input) { + return Awesomplete.FILTER_CONTAINS(data.value, input) + }, + + // filter function as specified in Awesomplete. Filters suggestion list on matching begin text. + // Awesomplete.FILTER_STARTSWITH filters on data.label, however + // this function filters on value and not on the shown label which may contain markup. + filterStartsWith: function (data, input) { + return Awesomplete.FILTER_STARTSWITH(data.value, input) + }, + + // Flatten JSON. + // { "a":{"b":{"c":[{"d":{"e":1}}]}}} becomes {"a.b.c":[{"d.e":1}]}. + // This function can be bind to configure it with extra options; + // bind({root: '', value: '', label: '

+
+ <%= 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/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex index 1b5cb5c4fd..e49adf8801 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -11,6 +11,80 @@ ) %> + <%= case decode(@log, @log.transaction) do %> + <% {:error, :contract_not_verified} -> %> +
<%= gettext "Decoded" %>
+
+
+ <%= gettext "To see decoded input data, the contract must be verified." %> + <%= case @log.transaction do %> + <% %{to_address: %{hash: hash}} -> %> + <%= gettext "Verify the contract " %><%= gettext "here" %> + <% _ -> %> + <%= nil %> + <% end %> +
+ <% {:error, :could_not_decode} -> %> +
<%= gettext "Decoded" %>
+
+
+ <%= gettext "Failed to decode log data." %> +
+ <% {:ok, method_id, text, mapping} -> %> +
<%= gettext "Decoded" %>
+
+ + + + + + + + + +
Method Id0x<%= method_id %>
Call<%= text %>
+
+ " class="table thead-light table-bordered"> + + + + + + + + <%= for {name, type, indexed?, value} <- mapping do %> + + + + + + + + <% end %> +
<%= gettext "Name" %><%= gettext "Type" %><%= gettext "Indexed?" %><%= gettext "Data" %>
+ <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> + <% :error -> %> + <%= nil %> + <% copy_text -> %> + + + + + + <% end %> + <%= name %><%= type %><%= indexed? %> +
<%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
+
+
+ <% _ -> %> + <%= nil %> + <% end %>
<%= gettext "Topics" %>
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/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex index 24b2d9e61a..65e785920f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -57,7 +57,7 @@
<%= gettext "Difficulty" %>
- <%= @block.difficulty |> Cldr.Number.to_string! %> + <%= @block.difficulty |> BlockScoutWeb.Cldr.Number.to_string! %>
@@ -65,7 +65,7 @@
<%= gettext "Total Difficulty" %>
-
<%= @block.total_difficulty |> Cldr.Number.to_string! %>
+
<%= @block.total_difficulty |> BlockScoutWeb.Cldr.Number.to_string! %>
@@ -97,14 +97,14 @@
<%= gettext "Gas Used" %>
- <%= @block.gas_used |> Cldr.Number.to_string! %> - (<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> Cldr.Number.to_string!(format: "#.#%") %>) + <%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %> + (<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)
<%= gettext "Gas Limit" %>
- <%= Cldr.Number.to_string!(@block.gas_limit) %> + <%= BlockScoutWeb.Cldr.Number.to_string!(@block.gas_limit) %>
<% end %> @@ -146,10 +146,10 @@

<%= gettext "Gas Used" %>

- <%= @block.gas_used |> Cldr.Number.to_string! %> - (<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> Cldr.Number.to_string!(format: "#.#%") %>) + <%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %> + (<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)

-

<%= @block.gas_limit |> Cldr.Number.to_string! %><%= gettext "Gas Limit" %>

+

<%= @block.gas_limit |> BlockScoutWeb.Cldr.Number.to_string! %><%= gettext "Gas Limit" %>

<% end %>
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/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 0c29001697..8285d429e0 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 @@
-
+
@@ -64,7 +64,7 @@ <%= gettext "Total blocks" %> - <%= Cldr.Number.to_string!(@block_count, format: "#,###") %> + <%= BlockScoutWeb.Cldr.Number.to_string!(@block_count, format: "#,###") %>
@@ -72,7 +72,7 @@ <%= gettext "Wallet addresses" %> - <%= Cldr.Number.to_string!(@address_count, format: "#,###") %> + <%= BlockScoutWeb.Cldr.Number.to_string!(@address_count, format: "#,###") %>
@@ -91,8 +91,8 @@ <%= gettext "Something went wrong, click to reload." %> -
- +
-<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %> \ No newline at end of file +<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %> + \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index b3b6ba0665..6c0281f5c7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -16,14 +16,15 @@ "> - - - - <%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %> +
<%= if not Explorer.Chain.finished_indexing?() do %>
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." %>
-
-
-
+
+ <%= 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/_decoded_input_body.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex index f009eeb2b3..9e68d7e1bd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex @@ -1,4 +1,5 @@ -" class="table thead-light table-bordered table-responsive transaction-info-table"> +
+
" class="table thead-light table-bordered"> @@ -7,49 +8,52 @@ -
<%= gettext "Method Id" %> 0x<%= @method_id %>Call <%= @text %>
+ + <%= unless Enum.empty?(@mapping) do %> - " class="table thead-light table-bordered table-responsive"> - - - - - - - <%= for {name, type, value} <- @mapping do %> +
+
<%= gettext "Name" %><%= gettext "Type" %><%= gettext "Data" %>
" class="table thead-light table-bordered"> + + + + + - - - - + - <% end %> -
<%= gettext "Name" %><%= gettext "Type" %><%= gettext "Data" %>
- <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> - <% :error -> %> - <%= nil %> - <% copy_text -> %> - - - - - - <% end %> - <%= name %><%= type %> - <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %> - <% :error -> %> -
- <%= gettext "Error rendering value" %> -
- <% value -> %> -
<%= value %>
+ <%= for {name, type, value} <- @mapping do %> +
+ <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> + <% :error -> %> + <%= nil %> + <% copy_text -> %> + + + + + <% end %> - -
+ + <%= name %> + <%= type %> + + <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %> + <% :error -> %> +
+ <%= gettext "Error rendering value" %> +
+ <% value -> %> +
<%= value %>
+ <% end %> + + + <% end %> + + <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex index d6882801ad..b70cbbb1fb 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -26,11 +26,11 @@ - <%= value(@transaction, include_label: false) %> + <%= value(@transaction, include_label: false) %>  <%= gettext "Ether" %> - <%= formatted_fee(@transaction, denomination: :ether, include_label: false) %> + <%= formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex index 73038c15db..3afa468b54 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex @@ -19,7 +19,7 @@ <%= @token_transfer |> BlockScoutWeb.AddressView.address_partial_selector(:to, @address, true) |> BlockScoutWeb.RenderHelpers.render_partial() %> - + <%= token_transfer_amount(@token_transfer) %> <%= link(token_symbol(@token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @token_transfer.token.contract_address_hash)) %> 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/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index fe347ee904..e804ec81d9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -177,7 +177,7 @@

<%= token_type_name(type)%><%= gettext " Token Transfer" %>

- <%= for transfer <- transaction_with_transfers.token_transfers do %> + <%= for transfer <- aggregate_token_transfers(transaction_with_transfers.token_transfers) do %>

<%= token_transfer_amount(transfer) %> 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/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex index 5a52821601..c63502669b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex @@ -1,7 +1,7 @@
-
<%= gettext "Address" %>
-
+
<%= gettext "Address" %>
+

<%= link( @log.address, @@ -11,10 +11,10 @@ ) %>

-
<%= gettext "Decoded" %>
-
<%= case decode(@log, @transaction) do %> <% {:error, :contract_not_verified} -> %> +
<%= gettext "Decoded" %>
+
<%= gettext "To see decoded input data, the contract must be verified." %> <%= case @transaction do %> @@ -25,10 +25,16 @@ <% end %>
<% {:error, :could_not_decode} -> %> +
<%= gettext "Decoded" %>
+
<%= gettext "Failed to decode log data." %>
+ <% {:error, :no_matching_function} -> %> + <%= nil %> <% {:ok, method_id, text, mapping} -> %> +
<%= gettext "Decoded" %>
+
@@ -39,49 +45,51 @@
Method Id<%= text %>
- " class="table thead-light table-bordered table-responsive"> - - - - - - - - <%= for {name, type, indexed?, value} <- mapping do %> +
+
<%= gettext "Name" %><%= gettext "Type" %><%= gettext "Indexed?" %><%= gettext "Data" %>
" class="table thead-light table-bordered"> - - - - - - - <% end %> -
- <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> - <% :error -> %> - <%= nil %> - <% copy_text -> %> - - - - - - <% end %> - <%= name %><%= type %><%= indexed? %> -
<%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
-
+ + <%= gettext "Name" %> + <%= gettext "Type" %> + <%= gettext "Indexed?" %> + <%= gettext "Data" %> + + <%= for {name, type, indexed?, value} <- mapping do %> + + + <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %> + <% :error -> %> + <%= nil %> + <% copy_text -> %> + + + + + + <% end %> + + <%= name %> + <%= type %> + <%= indexed? %> + +
<%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
+ + + <% end %> + +
<% _ -> %> <%= nil %> <% end %> -
<%= gettext "Topics" %>
-
+
<%= gettext "Topics" %>
+
<%= unless is_nil(@log.first_topic) do %>
@@ -109,10 +117,10 @@ <% end %>
-
+
<%= gettext "Data" %>
-
+
<%= unless is_nil(@log.data) do %>
<%= @log.data %> 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/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex index 7155e65206..3cdfaf1f9b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex @@ -1,3 +1,9 @@ defmodule BlockScoutWeb.AddressLogsView do use BlockScoutWeb, :view + + alias Explorer.Chain.Log + + def decode(log, transaction) do + Log.decode(log, transaction) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index f93b8bcb0b..15405beecb 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -222,7 +222,7 @@ defmodule BlockScoutWeb.AddressView do def incoming_transaction_count(address_hash) do address_hash |> Chain.address_to_incoming_transaction_count() - |> Cldr.Number.to_string!(format: "#,###") + |> BlockScoutWeb.Cldr.Number.to_string!(format: "#,###") end def trimmed_hash(%Hash{} = hash) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex index 8d88181a5d..bdc5e28b0e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.StatsView do alias BlockScoutWeb.API.RPC.RPCView def render("tokensupply.json", token_supply) do - RPCView.render("show.json", data: token_supply) + RPCView.render("show.json", data: Decimal.to_string(token_supply)) end def render("ethsupply.json", %{total_supply: total_supply}) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex index e475e7ace9..f30f385c86 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex @@ -15,7 +15,7 @@ defmodule BlockScoutWeb.BlockView do |> Enum.map(&Decimal.to_float(Wei.to(&1.gas_price, :gwei))) |> mean() |> Kernel.||(0) - |> Cldr.Number.to_string!() + |> BlockScoutWeb.Cldr.Number.to_string!() unit_text = gettext("Gwei") @@ -33,7 +33,7 @@ defmodule BlockScoutWeb.BlockView do # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Unit.to_string! spec case :erlang.phash2(1, 1) do 0 -> - Cldr.Unit.to_string!(unit) + BlockScoutWeb.Cldr.Unit.to_string!(unit) 1 -> # does not occur @@ -42,7 +42,7 @@ defmodule BlockScoutWeb.BlockView do end def formatted_gas(gas, format \\ []) do - Cldr.Number.to_string!(gas, format) + BlockScoutWeb.Cldr.Number.to_string!(gas, format) end def formatted_timestamp(%Block{timestamp: timestamp}) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/cldr/number.ex b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex similarity index 79% rename from apps/block_scout_web/lib/block_scout_web/views/cldr/number.ex rename to apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex index 2484646a74..e5b946e416 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/cldr/number.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Cldr.Number do +defmodule BlockScoutWeb.CldrHelper.Number do @moduledoc """ Work-arounds for `Cldr.Number` bugs """ @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.Cldr.Number do # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string spec case :erlang.phash2(1, 1) do 0 -> - Cldr.Number.to_string(decimal, options) + BlockScoutWeb.Cldr.Number.to_string(decimal, options) 1 -> # does not occur @@ -19,7 +19,7 @@ defmodule BlockScoutWeb.Cldr.Number do # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string! spec case :erlang.phash2(1, 1) do 0 -> - Cldr.Number.to_string!(decimal) + BlockScoutWeb.Cldr.Number.to_string!(decimal) 1 -> # does not occur @@ -31,7 +31,7 @@ defmodule BlockScoutWeb.Cldr.Number do # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string! spec case :erlang.phash2(1, 1) do 0 -> - Cldr.Number.to_string!(decimal, options) + BlockScoutWeb.Cldr.Number.to_string!(decimal, options) 1 -> # does not occur diff --git a/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex index d79be3b31e..2aad5b013c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.CurrencyHelpers do Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values. """ - alias BlockScoutWeb.Cldr.Number + alias BlockScoutWeb.CldrHelper.Number @doc """ Formats the given integer value to a currency format. diff --git a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex index fe12f04302..46252f72ce 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex @@ -23,6 +23,7 @@ defmodule BlockScoutWeb.InternalTransactionView do def type(%InternalTransaction{type: :call, call_type: :delegatecall}), do: gettext("Delegate Call") def type(%InternalTransaction{type: :call, call_type: :staticcall}), do: gettext("Static Call") def type(%InternalTransaction{type: :create}), do: gettext("Create") + def type(%InternalTransaction{type: :create2}), do: gettext("Create2") def type(%InternalTransaction{type: :selfdestruct}), do: gettext("Self-Destruct") def type(%InternalTransaction{type: :reward}), do: gettext("Reward") end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex index 1ad70179bf..33569f3a97 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.Helpers do """ alias BlockScoutWeb.{CurrencyHelpers} - alias Explorer.Chain.{Address, Token, TokenTransfer} + alias Explorer.Chain.{Address, Token} @doc """ Returns the token transfers' amount according to the token's type and decimals. @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.Tokens.Helpers do When the token's type is ERC-721, the function will return a string with the token_id that represents the ERC-721 token since this kind of token doesn't have amount and decimals. """ - def token_transfer_amount(%TokenTransfer{token: token, amount: amount, token_id: token_id}) do + def token_transfer_amount(%{token: token, amount: amount, token_id: token_id}) do do_token_transfer_amount(token, amount, token_id) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 1ed50d28ab..53d64555dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.TransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.{AddressView, BlockView, TabHelpers} - alias Cldr.Number + alias BlockScoutWeb.Cldr.Number alias Explorer.{Chain, Repo} alias Explorer.Chain.Block.Reward alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei} @@ -39,6 +39,46 @@ defmodule BlockScoutWeb.TransactionView do if type, do: {type, transaction_with_transfers} end + def aggregate_token_transfers(token_transfers) do + {transfers, nft_transfers} = + token_transfers + |> Enum.reduce({%{}, []}, fn token_transfer, acc -> + aggregate_reducer(token_transfer, acc) + end) + + final_transfers = Map.values(transfers) + + final_transfers ++ nft_transfers + end + + defp aggregate_reducer(%{amount: amount} = token_transfer, {acc1, acc2}) when is_nil(amount) do + new_entry = %{ + token: token_transfer.token, + amount: nil, + token_id: token_transfer.token_id + } + + {acc1, [new_entry | acc2]} + end + + defp aggregate_reducer(token_transfer, {acc1, acc2}) do + new_entry = %{ + token: token_transfer.token, + amount: token_transfer.amount, + token_id: token_transfer.token_id + } + + existing_entry = Map.get(acc1, token_transfer.token_contract_address, %{new_entry | amount: Decimal.new(0)}) + + new_acc1 = + Map.put(acc1, token_transfer.token_contract_address, %{ + new_entry + | amount: Decimal.add(new_entry.amount, existing_entry.amount) + }) + + {new_acc1, acc2} + end + def token_type_name(type) do case type do :erc20 -> gettext("ERC-20 ") @@ -100,7 +140,7 @@ defmodule BlockScoutWeb.TransactionView do %Block{consensus: true} -> {:ok, confirmations} = Chain.confirmations(block, named_arguments) - Cldr.Number.to_string!(confirmations, format: "#,###") + BlockScoutWeb.Cldr.Number.to_string!(confirmations, format: "#,###") end end @@ -156,7 +196,7 @@ defmodule BlockScoutWeb.TransactionView do end def gas(%type{gas: gas}) when is_transaction_type(type) do - Cldr.Number.to_string!(gas) + BlockScoutWeb.Cldr.Number.to_string!(gas) end def skip_decoding?(transaction) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex index 5cdbff8c0c..c9dfefa7b7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex @@ -5,7 +5,7 @@ defmodule BlockScoutWeb.WeiHelpers do import BlockScoutWeb.Gettext - alias BlockScoutWeb.Cldr + alias BlockScoutWeb.CldrHelper alias Explorer.Chain.Wei @valid_units ~w(wei gwei ether)a @@ -60,9 +60,9 @@ defmodule BlockScoutWeb.WeiHelpers do formatted_value = if Decimal.cmp(converted_value, 1_000_000_000_000) == :gt do - Cldr.Number.to_string!(converted_value, format: "0.###E+0") + CldrHelper.Number.to_string!(converted_value, format: "0.###E+0") else - Cldr.Number.to_string!(converted_value, format: "#,##0.##################") + CldrHelper.Number.to_string!(converted_value, format: "#,##0.##################") end if Keyword.get(options, :include_unit_label, true) do diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index e431d4ecf5..27a1e6aa74 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -50,7 +50,6 @@ defmodule BlockScoutWeb.Mixfile do defp extra_applications, do: [ - :ex_cldr, :logger, :runtime_tools ] @@ -65,21 +64,23 @@ defmodule BlockScoutWeb.Mixfile do # Integrates Absinthe subscriptions with Phoenix {:absinthe_phoenix, git: "https://github.com/ayrat555/absinthe_phoenix.git", branch: "master"}, # Plug support for Absinthe - {:absinthe_plug, git: "https://github.com/ayrat555/absinthe_plug.git", branch: "ab-allow-to-set-default-query"}, + {:absinthe_plug, git: "https://github.com/ayrat555/absinthe_plug.git", branch: "ab-enable-default-query"}, # Absinthe support for the Relay framework {:absinthe_relay, "~> 1.4"}, {:bypass, "~> 1.0", only: :test}, # To add (CORS)(https://www.w3.org/TR/cors/) {:cors_plug, "~> 2.0"}, - {:credo, "1.0.0", only: :test, runtime: false}, + {:credo, "~> 1.1", only: :test, runtime: false}, # For Absinthe to load data in batches {:dataloader, "~> 1.0.0"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, # Need until https://github.com/absinthe-graphql/absinthe_relay/pull/125 is released, then can be removed # The current `absinthe_relay` is compatible though as shown from that PR {:ecto, "~> 3.0", override: true}, - {:ex_cldr_numbers, "~> 1.0"}, - {:ex_cldr_units, "~> 1.0"}, + {:ex_cldr, "~> 2.7"}, + {:ex_cldr_numbers, "~> 2.6"}, + {:ex_cldr_units, "~> 2.5"}, + {:cldr_utils, "~> 2.3"}, {:ex_machina, "~> 2.1", only: [:test]}, # Code coverage {:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"}, @@ -101,7 +102,7 @@ defmodule BlockScoutWeb.Mixfile do {:phoenix, "~> 1.4"}, {:phoenix_ecto, "~> 4.0"}, {:phoenix_html, "~> 2.10"}, - {:phoenix_live_reload, "~> 1.0", only: [:dev]}, + {:phoenix_live_reload, "~> 1.2", only: [:dev]}, {:phoenix_pubsub, "~> 1.0"}, # use `:cowboy` for WebServer with `:plug` {:plug_cowboy, "~> 2.0"}, @@ -125,7 +126,7 @@ defmodule BlockScoutWeb.Mixfile do {:spandex_datadog, "~> 0.4.0"}, # `:spandex` tracing of `:phoenix` {:spandex_phoenix, "~> 0.3.1"}, - {:timex, "~> 3.4"}, + {:timex, "~> 3.6"}, {:wallaby, "~> 0.22", only: [:test], runtime: false}, # `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility {:wobserver, "~> 0.2.0", github: "poanetwork/wobserver", branch: "support-https"}, diff --git a/apps/block_scout_web/package-lock.json b/apps/block_scout_web/package-lock.json deleted file mode 100644 index 48e341a095..0000000000 --- a/apps/block_scout_web/package-lock.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "lockfileVersion": 1 -} diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 61d7465f49..11cf715872 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:144 +#: lib/block_scout_web/views/transaction_view.ex:184 msgid "(Awaiting internal transactions for status)" msgstr "" @@ -62,7 +62,7 @@ msgid "(query)" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:35 +#: lib/block_scout_web/templates/layout/app.html.eex:36 msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -87,7 +87,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:70 msgid "Accounts" msgstr "" @@ -157,7 +157,7 @@ msgid "Block Height: %{height}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:50 +#: lib/block_scout_web/templates/layout/app.html.eex:51 msgid "Block Mined, awaiting import..." msgstr "" @@ -178,19 +178,19 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:87 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:16 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:20 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:30 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 msgid "Blocks" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:49 +#: lib/block_scout_web/templates/layout/app.html.eex:50 msgid "Blocks Indexed" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:32 -#: lib/block_scout_web/templates/address/overview.html.eex:95 +#: lib/block_scout_web/templates/address/overview.html.eex:97 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:311 msgid "Blocks Validated" @@ -209,8 +209,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:144 -#: lib/block_scout_web/templates/address/overview.html.eex:152 +#: lib/block_scout_web/templates/address/overview.html.eex:146 +#: lib/block_scout_web/templates/address/overview.html.eex:154 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "Close" @@ -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 "" @@ -276,12 +276,12 @@ msgid "Contract Address Pending" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:221 +#: lib/block_scout_web/views/transaction_view.ex:261 msgid "Contract Call" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:260 msgid "Contract Creation" 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 "" @@ -319,7 +319,7 @@ msgid "Copy Txn Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:105 +#: lib/block_scout_web/templates/address/overview.html.eex:107 msgid "Created by" msgstr "" @@ -328,14 +328,6 @@ msgstr "" msgid "Curl" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113 -msgid "Data" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 @@ -364,19 +356,19 @@ msgid "Error trying to fetch balances." msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:148 +#: lib/block_scout_web/views/transaction_view.ex:188 msgid "Error: %{reason}" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:146 +#: lib/block_scout_web/views/transaction_view.ex:186 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_balance_card.html.eex:15 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21 -#: lib/block_scout_web/templates/layout/app.html.eex:55 +#: lib/block_scout_web/templates/layout/app.html.eex:56 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 #: lib/block_scout_web/templates/transaction/overview.html.eex:196 @@ -400,7 +392,7 @@ msgid "Fetching tokens..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:26 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:40 msgid "Forked Blocks (Reorgs)" msgstr "" @@ -454,7 +446,7 @@ msgid "IN" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:51 +#: lib/block_scout_web/templates/layout/app.html.eex:52 msgid "Indexing Tokens" msgstr "" @@ -474,7 +466,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:306 -#: lib/block_scout_web/views/transaction_view.ex:274 +#: lib/block_scout_web/views/transaction_view.ex:314 msgid "Internal Transactions" msgstr "" @@ -486,7 +478,7 @@ msgid "Inventory" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:52 +#: lib/block_scout_web/templates/layout/app.html.eex:53 msgid "Less than" msgstr "" @@ -501,21 +493,21 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:312 -#: lib/block_scout_web/views/transaction_view.ex:275 +#: lib/block_scout_web/views/transaction_view.ex:315 msgid "Logs" msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:31 -#: lib/block_scout_web/templates/layout/app.html.eex:53 +#: lib/block_scout_web/templates/layout/app.html.eex:54 #: lib/block_scout_web/views/address_view.ex:121 #: lib/block_scout_web/views/address_view.ex:121 msgid "Market Cap" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:129 -#: lib/block_scout_web/views/transaction_view.ex:129 +#: lib/block_scout_web/views/transaction_view.ex:169 +#: lib/block_scout_web/views/transaction_view.ex:169 msgid "Max of" msgstr "" @@ -555,9 +547,10 @@ msgid "Must be set to:" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:45 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:52 msgid "Name" msgstr "" @@ -604,9 +597,9 @@ msgid "Parent Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#: lib/block_scout_web/views/transaction_view.ex:143 -#: lib/block_scout_web/views/transaction_view.ex:177 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:58 +#: lib/block_scout_web/views/transaction_view.ex:183 +#: lib/block_scout_web/views/transaction_view.ex:217 msgid "Pending" msgstr "" @@ -622,13 +615,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:24 -#: lib/block_scout_web/templates/layout/app.html.eex:54 +#: lib/block_scout_web/templates/layout/app.html.eex:55 msgid "Price" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:143 +#: lib/block_scout_web/templates/address/overview.html.eex:145 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 msgid "QR Code" @@ -669,8 +662,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:122 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:139 msgid "Search" msgstr "" @@ -693,7 +686,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:145 +#: lib/block_scout_web/views/transaction_view.ex:185 msgid "Success" msgstr "" @@ -774,7 +767,7 @@ msgid "To" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:6 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:20 msgid "Toggle navigation" msgstr "" @@ -798,7 +791,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 -#: lib/block_scout_web/views/transaction_view.ex:219 +#: lib/block_scout_web/views/transaction_view.ex:259 msgid "Token Transfer" msgstr "" @@ -808,7 +801,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/tokens/overview_view.ex:35 -#: lib/block_scout_web/views/transaction_view.ex:273 +#: lib/block_scout_web/views/transaction_view.ex:313 msgid "Token Transfers" msgstr "" @@ -825,12 +818,6 @@ msgstr "" msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:83 -msgid "Topics" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:67 msgid "Total Difficulty" @@ -848,7 +835,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:222 +#: lib/block_scout_web/views/transaction_view.ex:262 msgid "Transaction" msgstr "" @@ -873,7 +860,7 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:305 msgid "Transactions" msgstr "" @@ -900,7 +887,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:37 msgid "Uncles" msgstr "" @@ -915,7 +902,7 @@ msgid "Used" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:39 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:53 msgid "Validated" msgstr "" @@ -996,12 +983,12 @@ msgid "Yes" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:111 +#: lib/block_scout_web/templates/address/overview.html.eex:113 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 +1006,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 "" @@ -1039,14 +1026,7 @@ msgid "Delegate Call" 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/views/internal_transaction_view.ex:27 -msgid "Reward" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/views/internal_transaction_view.ex:26 msgid "Self-Destruct" msgstr "" @@ -1058,7 +1038,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 "" @@ -1069,22 +1048,17 @@ msgid "Loading...." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:64 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 msgid "APIs" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:68 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:82 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 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:87 msgid "RPC" msgstr "" @@ -1134,48 +1108,41 @@ msgid "Static Call" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:14 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:28 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:34 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:28 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Decoded" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47 -msgid "Indexed?" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 -msgid "Type" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:4 msgid "Method Id" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19 msgid "To see decoded input data, the contract must be verified." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1 -msgid "Transaction Info" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16 msgid "Transaction Inputs" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22 msgid "Verify the contract " msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22 msgid "here" @@ -1187,26 +1154,10 @@ msgid "Failed to decode input data." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:46 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49 msgid "Error rendering value" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:28 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58 -msgid "Copy Value" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 -msgid "Failed to decode log data." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -msgid "Log Data" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 @@ -1277,7 +1228,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 +1358,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 "" @@ -1432,7 +1383,7 @@ msgid "Contract Libraries" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:88 +#: lib/block_scout_web/templates/address/overview.html.eex:89 msgid "Last Balance Update: Block #" msgstr "" @@ -1478,13 +1429,13 @@ msgid "Incoming Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:119 +#: lib/block_scout_web/templates/address/overview.html.eex:121 msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:96 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:116 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1548,7 +1499,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:276 +#: lib/block_scout_web/views/transaction_view.ex:316 msgid "Raw Trace" msgstr "" @@ -1559,7 +1510,7 @@ msgstr "" #, elixir-format #: -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:40 +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:39 msgid "Page" msgstr "" @@ -1577,7 +1528,7 @@ msgstr "" #, elixir-format #: -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:40 +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:39 msgid "of" msgstr "" @@ -1587,27 +1538,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 "" @@ -1710,7 +1661,7 @@ msgid "ETH RPC API Documentation" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:92 msgid "Eth RPC" msgstr "" @@ -1745,8 +1696,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 "" @@ -1756,22 +1707,17 @@ 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 +#: lib/block_scout_web/views/transaction_view.ex:84 msgid "ERC-20 " msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:45 +#: lib/block_scout_web/views/transaction_view.ex:85 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 +1750,67 @@ 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 "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:63 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:65 +msgid "Copy Value" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/internal_transaction_view.ex:26 +msgid "Create2" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:118 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 +msgid "Data" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:31 +msgid "Failed to decode log data." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:52 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:54 +msgid "Indexed?" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:47 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:49 +msgid "Log Data" +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/views/internal_transaction_view.ex:28 +msgid "Reward" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:88 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 +msgid "Topics" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:51 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53 +msgid "Type" +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..f9e5473ee4 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 @@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:144 +#: lib/block_scout_web/views/transaction_view.ex:184 msgid "(Awaiting internal transactions for status)" msgstr "" @@ -62,7 +62,7 @@ msgid "(query)" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:35 +#: lib/block_scout_web/templates/layout/app.html.eex:36 msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -87,7 +87,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:70 msgid "Accounts" msgstr "" @@ -157,7 +157,7 @@ msgid "Block Height: %{height}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:50 +#: lib/block_scout_web/templates/layout/app.html.eex:51 msgid "Block Mined, awaiting import..." msgstr "" @@ -178,19 +178,19 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:87 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:16 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:20 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:30 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 msgid "Blocks" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:49 +#: lib/block_scout_web/templates/layout/app.html.eex:50 msgid "Blocks Indexed" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:32 -#: lib/block_scout_web/templates/address/overview.html.eex:95 +#: lib/block_scout_web/templates/address/overview.html.eex:97 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:311 msgid "Blocks Validated" @@ -209,8 +209,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:144 -#: lib/block_scout_web/templates/address/overview.html.eex:152 +#: lib/block_scout_web/templates/address/overview.html.eex:146 +#: lib/block_scout_web/templates/address/overview.html.eex:154 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "Close" @@ -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 "" @@ -276,12 +276,12 @@ msgid "Contract Address Pending" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:221 +#: lib/block_scout_web/views/transaction_view.ex:261 msgid "Contract Call" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:260 msgid "Contract Creation" 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 "" @@ -319,7 +319,7 @@ msgid "Copy Txn Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:105 +#: lib/block_scout_web/templates/address/overview.html.eex:107 msgid "Created by" msgstr "" @@ -328,14 +328,6 @@ msgstr "" msgid "Curl" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113 -msgid "Data" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 @@ -364,19 +356,19 @@ msgid "Error trying to fetch balances." msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:148 +#: lib/block_scout_web/views/transaction_view.ex:188 msgid "Error: %{reason}" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:146 +#: lib/block_scout_web/views/transaction_view.ex:186 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_balance_card.html.eex:15 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21 -#: lib/block_scout_web/templates/layout/app.html.eex:55 +#: lib/block_scout_web/templates/layout/app.html.eex:56 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 #: lib/block_scout_web/templates/transaction/overview.html.eex:196 @@ -400,7 +392,7 @@ msgid "Fetching tokens..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:26 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:40 msgid "Forked Blocks (Reorgs)" msgstr "" @@ -454,7 +446,7 @@ msgid "IN" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:51 +#: lib/block_scout_web/templates/layout/app.html.eex:52 msgid "Indexing Tokens" msgstr "" @@ -474,7 +466,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:306 -#: lib/block_scout_web/views/transaction_view.ex:274 +#: lib/block_scout_web/views/transaction_view.ex:314 msgid "Internal Transactions" msgstr "" @@ -486,7 +478,7 @@ msgid "Inventory" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/app.html.eex:52 +#: lib/block_scout_web/templates/layout/app.html.eex:53 msgid "Less than" msgstr "" @@ -501,21 +493,21 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:312 -#: lib/block_scout_web/views/transaction_view.ex:275 +#: lib/block_scout_web/views/transaction_view.ex:315 msgid "Logs" msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:31 -#: lib/block_scout_web/templates/layout/app.html.eex:53 +#: lib/block_scout_web/templates/layout/app.html.eex:54 #: lib/block_scout_web/views/address_view.ex:121 #: lib/block_scout_web/views/address_view.ex:121 msgid "Market Cap" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:129 -#: lib/block_scout_web/views/transaction_view.ex:129 +#: lib/block_scout_web/views/transaction_view.ex:169 +#: lib/block_scout_web/views/transaction_view.ex:169 msgid "Max of" msgstr "" @@ -555,9 +547,10 @@ msgid "Must be set to:" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:45 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:52 msgid "Name" msgstr "" @@ -604,9 +597,9 @@ msgid "Parent Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#: lib/block_scout_web/views/transaction_view.ex:143 -#: lib/block_scout_web/views/transaction_view.ex:177 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:58 +#: lib/block_scout_web/views/transaction_view.ex:183 +#: lib/block_scout_web/views/transaction_view.ex:217 msgid "Pending" msgstr "" @@ -622,13 +615,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:24 -#: lib/block_scout_web/templates/layout/app.html.eex:54 +#: lib/block_scout_web/templates/layout/app.html.eex:55 msgid "Price" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:143 +#: lib/block_scout_web/templates/address/overview.html.eex:145 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 msgid "QR Code" @@ -669,8 +662,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:122 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:139 msgid "Search" msgstr "" @@ -693,7 +686,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:145 +#: lib/block_scout_web/views/transaction_view.ex:185 msgid "Success" msgstr "" @@ -774,7 +767,7 @@ msgid "To" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:6 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:20 msgid "Toggle navigation" msgstr "" @@ -798,7 +791,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 -#: lib/block_scout_web/views/transaction_view.ex:219 +#: lib/block_scout_web/views/transaction_view.ex:259 msgid "Token Transfer" msgstr "" @@ -808,7 +801,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/tokens/overview_view.ex:35 -#: lib/block_scout_web/views/transaction_view.ex:273 +#: lib/block_scout_web/views/transaction_view.ex:313 msgid "Token Transfers" msgstr "" @@ -825,12 +818,6 @@ msgstr "" msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:83 -msgid "Topics" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:67 msgid "Total Difficulty" @@ -848,7 +835,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:222 +#: lib/block_scout_web/views/transaction_view.ex:262 msgid "Transaction" msgstr "" @@ -873,7 +860,7 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:305 msgid "Transactions" msgstr "" @@ -900,7 +887,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:37 msgid "Uncles" msgstr "" @@ -915,7 +902,7 @@ msgid "Used" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:39 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:53 msgid "Validated" msgstr "" @@ -996,12 +983,12 @@ msgid "Yes" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:111 +#: lib/block_scout_web/templates/address/overview.html.eex:113 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 +1006,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 "" @@ -1039,14 +1026,7 @@ msgid "Delegate Call" 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/views/internal_transaction_view.ex:27 -msgid "Reward" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/views/internal_transaction_view.ex:26 msgid "Self-Destruct" msgstr "" @@ -1058,7 +1038,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 "" @@ -1069,22 +1048,17 @@ msgid "Loading...." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:64 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 msgid "APIs" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:68 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:82 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 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:87 msgid "RPC" msgstr "" @@ -1134,48 +1108,41 @@ msgid "Static Call" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:14 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:28 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:34 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:28 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Decoded" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47 -msgid "Indexed?" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 -msgid "Type" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:4 msgid "Method Id" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19 msgid "To see decoded input data, the contract must be verified." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1 -msgid "Transaction Info" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16 msgid "Transaction Inputs" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22 msgid "Verify the contract " msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22 msgid "here" @@ -1187,25 +1154,10 @@ msgid "Failed to decode input data." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:46 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49 msgid "Error rendering value" msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:28 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58 -msgid "Copy Value" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 -msgid "Failed to decode log data." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -msgid "Log Data" -msgstr "" +"" #, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 @@ -1277,7 +1229,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 +1359,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 "" @@ -1432,7 +1384,7 @@ msgid "Contract Libraries" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:88 +#: lib/block_scout_web/templates/address/overview.html.eex:89 msgid "Last Balance Update: Block #" msgstr "" @@ -1478,13 +1430,13 @@ msgid "Incoming Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:119 +#: lib/block_scout_web/templates/address/overview.html.eex:121 msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:96 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:116 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1548,7 +1500,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:276 +#: lib/block_scout_web/views/transaction_view.ex:316 msgid "Raw Trace" msgstr "" @@ -1559,7 +1511,7 @@ msgstr "" #, elixir-format #: -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:40 +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:39 msgid "Page" msgstr "" @@ -1577,7 +1529,7 @@ msgstr "" #, elixir-format #: -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:40 +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:39 msgid "of" msgstr "" @@ -1587,27 +1539,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 "" @@ -1710,7 +1662,7 @@ msgid "ETH RPC API Documentation" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:92 msgid "Eth RPC" msgstr "" @@ -1745,8 +1697,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 "" @@ -1756,22 +1708,17 @@ 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 +#: lib/block_scout_web/views/transaction_view.ex:84 msgid "ERC-20 " msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:45 +#: lib/block_scout_web/views/transaction_view.ex:85 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 +1751,67 @@ 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 "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:63 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:65 +msgid "Copy Value" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/internal_transaction_view.ex:26 +msgid "Create2" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:118 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 +msgid "Data" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:31 +msgid "Failed to decode log data." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:52 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:54 +msgid "Indexed?" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:47 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:49 +msgid "Log Data" +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/views/internal_transaction_view.ex:28 +msgid "Reward" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:88 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 +msgid "Topics" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:51 +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53 +msgid "Type" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/errors.pot b/apps/block_scout_web/priv/gettext/errors.pot index ca584666e5..cdaaac6299 100644 --- a/apps/block_scout_web/priv/gettext/errors.pot +++ b/apps/block_scout_web/priv/gettext/errors.pot @@ -1,52 +1,52 @@ -## This file is a PO Template file. -## -## `msgid`s here are often extracted from source code. -## Add new translations manually only if they're dynamic -## translations that can't be statically extracted. -## -## Run `mix gettext.extract` to bring this file up to -## date. Leave `msgstr`s empty as changing them here as no -## effect: edit them in PO (`.po`) files instead. -## From Ecto.Changeset.cast/4 +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 msgid "can't be blank" msgstr "" -## From Ecto.Changeset.unique_constraint/3 +## From Ecto.Changeset.unique_constraint/3 msgid "has already been taken" msgstr "" -## From Ecto.Changeset.put_change/3 +## From Ecto.Changeset.put_change/3 msgid "is invalid" msgstr "" -## From Ecto.Changeset.validate_acceptance/3 +## From Ecto.Changeset.validate_acceptance/3 msgid "must be accepted" msgstr "" -## From Ecto.Changeset.validate_format/3 +## From Ecto.Changeset.validate_format/3 msgid "has invalid format" msgstr "" -## From Ecto.Changeset.validate_subset/3 +## From Ecto.Changeset.validate_subset/3 msgid "has an invalid entry" msgstr "" -## From Ecto.Changeset.validate_exclusion/3 +## From Ecto.Changeset.validate_exclusion/3 msgid "is reserved" msgstr "" -## From Ecto.Changeset.validate_confirmation/3 +## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" msgstr "" -## From Ecto.Changeset.no_assoc_constraint/3 +## From Ecto.Changeset.no_assoc_constraint/3 msgid "is still associated with this entry" msgstr "" msgid "are still associated with this entry" msgstr "" -## From Ecto.Changeset.validate_length/3 +## From Ecto.Changeset.validate_length/3 msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" msgstr[0] "" @@ -77,7 +77,7 @@ msgid_plural "should have at most %{count} item(s)" msgstr[0] "" msgstr[1] "" -## From Ecto.Changeset.validate_number/3 +## From Ecto.Changeset.validate_number/3 msgid "must be less than %{number}" msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index a29d6b9be6..63a27c7f7b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -13,10 +13,16 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do assert html_response(conn, 422) end - test "with valid address hash without address", %{conn: conn} do - conn = get(conn, address_transaction_path(conn, :index, "0x8bf38d4764929064f2d4d3a56520a76ab3df415b")) + test "with valid address hash without address in the DB", %{conn: conn} do + conn = + get( + conn, + address_transaction_path(conn, :index, "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", %{"type" => "JSON"}) + ) - assert html_response(conn, 404) + assert json_response(conn, 200) + transaction_tiles = json_response(conn, 200)["items"] + assert transaction_tiles |> length() == 0 end test "returns transactions for the address", %{conn: conn} do 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/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs index 41735f685a..19631ff746 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs @@ -1,6 +1,15 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do use BlockScoutWeb.ConnCase + alias Explorer.{Chain, PagingOptions} + + setup do + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()}) + + :ok + end + describe "GET last_block_status/0" do test "returns error when there are no blocks in db", %{conn: conn} do request = get(conn, api_v1_health_path(conn, :health)) @@ -32,19 +41,50 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do end test "returns ok when last block is not stale", %{conn: conn} do - insert(:block, consensus: true, timestamp: DateTime.utc_now()) + block1 = insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 2) + insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1) request = get(conn, api_v1_health_path(conn, :health)) assert request.status == 200 + result = Poison.decode!(request.resp_body) + + assert result["healthy"] == true + assert %{ - "healthy" => true, - "data" => %{ - "latest_block_number" => _, - "latest_block_inserted_at" => _ - } - } = Poison.decode!(request.resp_body) + "latest_block_number" => to_string(block1.number), + "latest_block_inserted_at" => to_string(block1.timestamp), + "cache_latest_block_number" => to_string(block1.number), + "cache_latest_block_inserted_at" => to_string(block1.timestamp) + } == result["data"] end end + + test "return error when cache is stale", %{conn: conn} do + stale_block = insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50), number: 3) + state_block_hash = stale_block.hash + + assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1}) + + insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1) + + assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1}) + + request = get(conn, api_v1_health_path(conn, :health)) + + assert request.status == 500 + + assert %{ + "healthy" => false, + "error_code" => 5001, + "error_title" => "blocks fetching is stuck", + "error_description" => + "There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", + "data" => %{ + "latest_block_number" => _, + "latest_block_inserted_at" => _ + } + } = Poison.decode!(request.resp_body) + end end diff --git a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs index 2a3e5492ed..69cca69f41 100644 --- a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs @@ -253,4 +253,34 @@ defmodule BlockScoutWeb.TransactionViewTest do assert TransactionView.current_tab_name(logs_path) == "Logs" end end + + describe "aggregate_token_transfers/1" do + test "aggregates token transfers" do + transaction = + :transaction + |> insert() + |> with_block() + + token_transfer = insert(:token_transfer, transaction: transaction, amount: Decimal.new(1)) + + result = TransactionView.aggregate_token_transfers([token_transfer, token_transfer, token_transfer]) + + assert Enum.count(result) == 1 + assert List.first(result).amount == Decimal.new(3) + end + + test "does not aggregate NFT tokens" do + transaction = + :transaction + |> insert() + |> with_block() + + token_transfer = insert(:token_transfer, transaction: transaction, amount: nil) + + result = TransactionView.aggregate_token_transfers([token_transfer, token_transfer, token_transfer]) + + assert Enum.count(result) == 3 + assert List.first(result).amount == nil + end + end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index ab725c9c73..e0a6672be8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -5,7 +5,7 @@ defmodule EthereumJSONRPC.Geth do import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] - alias EthereumJSONRPC.{FetchedBalance, FetchedCode} + alias EthereumJSONRPC.{FetchedBalance, FetchedCode, Transactions} alias EthereumJSONRPC.Geth.{Calls, Tracer} @behaviour EthereumJSONRPC.Variant @@ -46,12 +46,31 @@ defmodule EthereumJSONRPC.Geth do def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore @doc """ - Pending transaction fetching is not supported currently for Geth. - - To signal to the caller that fetching is not supported, `:ignore` is returned. + Fetches the pending transactions from the Geth node. """ @impl EthereumJSONRPC.Variant - def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore + def fetch_pending_transactions(json_rpc_named_arguments) do + with {:ok, transaction_data} <- + %{id: 1, method: "txpool_content", params: []} |> request() |> json_rpc(json_rpc_named_arguments) do + transactions_params = + transaction_data["pending"] + |> Enum.flat_map(fn {_address, nonce_transactions_map} -> + nonce_transactions_map + |> Enum.map(fn {_nonce, transaction} -> + transaction + end) + end) + |> Transactions.to_elixir() + |> Transactions.elixir_to_params() + |> Enum.map(fn params -> + # txpool_content always returns transaction with 0x0000000000000000000000000000000000000000000000000000000000000000 value in block hash and index is null. + # https://github.com/ethereum/go-ethereum/issues/19897 + %{params | block_hash: nil, index: nil} + end) + + {:ok, transactions_params} + end + end defp debug_trace_transaction_requests(id_to_params) when is_map(id_to_params) do Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex index 1555ea6c8f..c83b157324 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex @@ -426,13 +426,14 @@ defmodule EthereumJSONRPC.Geth.Call do "transactionHash" => transaction_hash, "index" => index, "traceAddress" => trace_address, - "type" => "create" = type, + "type" => type, "from" => from_address_hash, "error" => error, "gas" => gas, "init" => init, "value" => value - }) do + }) + when type in ~w(create create2) do %{ block_number: block_number, transaction_index: transaction_index, @@ -454,7 +455,7 @@ defmodule EthereumJSONRPC.Geth.Call do "transactionHash" => transaction_hash, "index" => index, "traceAddress" => trace_address, - "type" => "create", + "type" => type, "from" => from_address_hash, "createdContractAddressHash" => created_contract_address_hash, "gas" => gas, @@ -462,14 +463,15 @@ defmodule EthereumJSONRPC.Geth.Call do "init" => init, "createdContractCode" => created_contract_code, "value" => value - }) do + }) + when type in ~w(create create2) do %{ block_number: block_number, transaction_index: transaction_index, transaction_hash: transaction_hash, index: index, trace_address: trace_address, - type: "create", + type: type, from_address_hash: from_address_hash, gas: gas, gas_used: gas_used, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex index 6545250b4a..cbc43fa71d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex @@ -113,6 +113,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do end defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx) + defp op(%{"op" => "CREATE2"} = log, ctx), do: create_op(log, ctx, "create2") defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx) defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx) defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx) @@ -155,7 +156,8 @@ defmodule EthereumJSONRPC.Geth.Tracer do defp create_op( %{"stack" => log_stack, "memory" => log_memory}, - %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx + %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx, + type \\ "create" ) do [value, input_offset, input_length | _] = Enum.reverse(log_stack) @@ -165,7 +167,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do |> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2) call = %{ - "type" => "create", + "type" => type, "from" => nil, "traceAddress" => Enum.reverse(trace_address), "init" => "0x" <> init, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index f021bfa153..7992c0e63c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -353,4 +353,8 @@ defmodule EthereumJSONRPC.Transaction do _ -> {key, quantity_to_integer(chain_id)} end end + + defp entry_to_elixir(_) do + {nil, nil} + end end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 7c2299c4a8..e3949457e9 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -64,7 +64,7 @@ defmodule EthereumJsonrpc.MixProject do # WebSocket-server for testing `EthereumJSONRPC.WebSocket.WebSocketClient`. {:cowboy, "~> 2.0", only: [:dev, :test]}, # Style Checking - {:credo, "1.0.0", only: :test, runtime: false}, + {:credo, "~> 1.1", only: :test, runtime: false}, # Static Type Checking {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, # Code coverage @@ -82,7 +82,7 @@ defmodule EthereumJsonrpc.MixProject do # `:spandex` integration with Datadog {:spandex_datadog, "~> 0.4.0"}, # Convert unix timestamps in JSONRPC to DateTimes - {:timex, "~> 3.4"}, + {:timex, "~> 3.6"}, # Encode/decode function names and arguments {:ex_abi, "~> 0.1.18"}, # `:verify_fun` for `Socket.Web.connect` diff --git a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js index 15db7d8ff9..5c3c98f871 100644 --- a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js +++ b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js @@ -91,6 +91,9 @@ case 'CREATE': this.createOp(log); break; + case 'CREATE2': + this.create2Op(log); + break; case 'SELFDESTRUCT': this.selfDestructOp(log, db); break; @@ -127,7 +130,7 @@ const ret = log.stack.peek(0); if (!ret.equals(0)) { - if (call.type === 'create') { + if (call.type === 'create' || call.type === 'create2') { call.createdContractAddressHash = toHex(toAddress(ret.toString(16))); call.createdContractCode = toHex(db.getCode(toAddress(ret.toString(16)))); } else { @@ -162,6 +165,21 @@ this.callStack.push(call); }, + create2Op(log) { + const inputOffset = log.stack.peek(1).valueOf(); + const inputLength = log.stack.peek(2).valueOf(); + const inputEnd = inputOffset + inputLength; + const stackValue = log.stack.peek(0); + + const call = { + type: 'create2', + from: toHex(log.contract.getAddress()), + init: toHex(log.memory.slice(inputOffset, inputEnd)), + valueBigInt: bigInt(stackValue.toString(10)) + }; + this.callStack.push(call); + }, + selfDestructOp(log, db) { const contractAddress = log.contract.getAddress(); @@ -243,6 +261,9 @@ case 'CREATE': result = this.ctxToCreate(ctx, db); break; + case 'CREATE2': + result = this.ctxToCreate2(ctx, db); + break; } return result; @@ -292,6 +313,22 @@ return result; }, + ctxToCreate2(ctx, db) { + const result = { + type: 'create2', + from: toHex(ctx.from), + init: toHex(ctx.input), + valueBigInt: bigInt(ctx.value.toString(10)), + gasBigInt: bigInt(ctx.gas), + gasUsedBigInt: bigInt(ctx.gasUsed) + }; + + this.putBottomChildCalls(result); + this.putErrorOrCreatedContract(result, ctx, db); + + return result; + }, + putBottomChildCalls(result) { const bottomCall = this.bottomCall(); const bottomChildCalls = bottomCall.calls; @@ -422,4 +459,3 @@ call.gasUsed = '0x' + gasUsedBigInt.toString(16); } } - diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 626583fa01..32a7f84df6 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -84,8 +84,133 @@ defmodule EthereumJSONRPC.GethTest do end describe "fetch_pending_transactions/1" do - test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do - EthereumJSONRPC.Geth.fetch_pending_transactions(json_rpc_named_arguments) + @tag :no_geth + test "fetches pending transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> + {:ok, + %{ + "pending" => %{ + "0xC99f4e9cFf697ca6717ad9cE8bA4A138e0e55109" => %{ + "4656" => %{ + "blockHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber" => nil, + "from" => "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + "gas" => "0x3d0900", + "gasPrice" => "0x3b9aca00", + "hash" => "0x2b8cfd76a31b942e51b6265c791c860e2840b11f8c2fcfa1c9dfe53dea4c3102", + "input" => + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000030af6932dec7c4eaf4b966059e74cc7a1767ba93e62f2d83a7dba5bb785b6efd25e8ab7d2e8798e7ecc27df96380d77a0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000b29e5229b86fbb3a8e45e406b64226c3d49389804a6f7449325fae232d6623000000000000000000000000000000000000000000000000000000000000006097e4c1ed443f430b1d8ad66e565a960fade76e3e177b4120186bdad2fcfa43e134de3abdc0272c9433af94833fec73260c261cf41422e83d958787b62144478bc44ab84d1ddba7a462d355057f3be8ab914a195ac1a637c4fb8503c441dadb45", + "nonce" => "0x1230", + "r" => "0x81345ae149171f4cb4ab868f0ad637d033c96c4659b190b86a39725c8299c947", + "s" => "0x31450678841d7206fa02b564a641420262cc98c8ea0e32c4cb0e97208d3f9feb", + "to" => "0xf003a84d6890202663c0fd80954e836fcf21e004", + "transactionIndex" => "0x0", + "v" => "0x1b", + "value" => "0xb5e620f480000" + }, + "4657" => %{ + "blockHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber" => nil, + "from" => "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + "gas" => "0x3d0900", + "gasPrice" => "0x3b9aca00", + "hash" => "0x7c3ea924740e996bf552a8dded903ba4258b69d30bf5e6dca6ec86ebc60b8151", + "input" => + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000030a25723bca32f88a73abc7eb153cee248effd563d87efe12e08e8a33f74047afc28c30ab9c74bddeb6f0558628b8bf200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020009c56025b2796cdc72f91836278a471590b774462adddd1c87a0b6f84b787990000000000000000000000000000000000000000000000000000000000000060aa53b46c8b57aed7c4c0fdf3f650ec3bb330591929bc813610656882e3203157c22b50d0d0b0316a8712c00fe4f0e0c509613114f5d24c0419a4e8188f2489678b05dccf72a67957785e8e250092c8787f049f7e20b1414a633595a56c98ff82", + "nonce" => "0x1231", + "r" => "0xee1eb895262d12ef5c4ee3cbf9b36de3903bc3a1343f0a312bd19edacc4bb877", + "s" => "0xfcb87efe4c3984a3e1d3f4fb10ce41e59f65e21fbd9206a1648ec73fa0a2206", + "to" => "0xf003a84d6890202663c0fd80954e836fcf21e004", + "transactionIndex" => "0x0", + "v" => "0x1b", + "value" => "0xb5e620f480000" + }, + "4658" => %{ + "blockHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber" => nil, + "from" => "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + "gas" => "0x3d0900", + "gasPrice" => "0x3b9aca00", + "hash" => "0xe699a58ef4986f2dbdc102acf73b35392aff9ce43fd226000526955e19c0b06e", + "input" => + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000308eb3ed9e686f6bc1fe2d8ce3fea37fb3a66a9c67b91ef15ba6bd7da0eed73288f72577edea2b7ded5855ca8a56b1e01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000051afe6c51e2175a62afbd66d293e8a7509943d5cd6f851f59923a61a186e80000000000000000000000000000000000000000000000000000000000000060a063498e8db2e75e0a193de89ad2947111d677c9501e75c34a64fcee8fe5a7c7607929fc6bce943d64f1039e1d1f325f02d1e5d71f86ca976c9ab79d19f0fd0e530a5210fbe131087ba1f1b3c92abc4a0dd7c8a47c3c276fac3e09bca964fd74", + "nonce" => "0x1232", + "r" => "0xe95bc86fc32cc591677c7ec9ca49f1dc33a31427235c1c41dbb7a3a957b55599", + "s" => "0xe8b41a6440d0fe6d0ec1f40982394a2d641b19b983aad49e45614e5f3a1abc9", + "to" => "0xf003a84d6890202663c0fd80954e836fcf21e004", + "transactionIndex" => "0x0", + "v" => "0x1c", + "value" => "0xb5e620f480000" + } + } + }, + "queued" => %{} + }} + end) + + assert {:ok, + [ + %{ + block_hash: nil, + block_number: nil, + from_address_hash: "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + gas: 4_000_000, + gas_price: 1_000_000_000, + hash: "0x2b8cfd76a31b942e51b6265c791c860e2840b11f8c2fcfa1c9dfe53dea4c3102", + index: 0, + input: + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000030af6932dec7c4eaf4b966059e74cc7a1767ba93e62f2d83a7dba5bb785b6efd25e8ab7d2e8798e7ecc27df96380d77a0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000b29e5229b86fbb3a8e45e406b64226c3d49389804a6f7449325fae232d6623000000000000000000000000000000000000000000000000000000000000006097e4c1ed443f430b1d8ad66e565a960fade76e3e177b4120186bdad2fcfa43e134de3abdc0272c9433af94833fec73260c261cf41422e83d958787b62144478bc44ab84d1ddba7a462d355057f3be8ab914a195ac1a637c4fb8503c441dadb45", + nonce: 4656, + r: + 58_440_860_745_466_360_584_510_362_592_650_991_653_332_571_230_597_223_185_413_246_840_900_756_818_247, + s: + 22_285_286_687_634_777_993_513_656_263_235_057_426_117_768_584_265_280_722_872_863_042_386_096_267_243, + to_address_hash: "0xf003a84d6890202663c0fd80954e836fcf21e004", + transaction_index: 0, + v: 27, + value: 3_200_000_000_000_000 + }, + %{ + block_hash: nil, + block_number: nil, + from_address_hash: "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + gas: 4_000_000, + gas_price: 1_000_000_000, + hash: "0x7c3ea924740e996bf552a8dded903ba4258b69d30bf5e6dca6ec86ebc60b8151", + index: 0, + input: + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000030a25723bca32f88a73abc7eb153cee248effd563d87efe12e08e8a33f74047afc28c30ab9c74bddeb6f0558628b8bf200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020009c56025b2796cdc72f91836278a471590b774462adddd1c87a0b6f84b787990000000000000000000000000000000000000000000000000000000000000060aa53b46c8b57aed7c4c0fdf3f650ec3bb330591929bc813610656882e3203157c22b50d0d0b0316a8712c00fe4f0e0c509613114f5d24c0419a4e8188f2489678b05dccf72a67957785e8e250092c8787f049f7e20b1414a633595a56c98ff82", + nonce: 4657, + r: + 107_704_737_317_141_024_268_971_404_113_297_355_261_066_880_504_936_960_891_977_784_149_226_505_877_623, + s: + 7_144_300_886_174_743_587_831_226_472_052_852_957_529_607_874_128_062_849_708_955_356_153_894_281_734, + to_address_hash: "0xf003a84d6890202663c0fd80954e836fcf21e004", + transaction_index: 0, + v: 27, + value: 3_200_000_000_000_000 + }, + %{ + block_hash: nil, + block_number: nil, + from_address_hash: "0xc99f4e9cff697ca6717ad9ce8ba4a138e0e55109", + gas: 4_000_000, + gas_price: 1_000_000_000, + hash: "0xe699a58ef4986f2dbdc102acf73b35392aff9ce43fd226000526955e19c0b06e", + index: 0, + input: + "0xc47e300d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000308eb3ed9e686f6bc1fe2d8ce3fea37fb3a66a9c67b91ef15ba6bd7da0eed73288f72577edea2b7ded5855ca8a56b1e01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000051afe6c51e2175a62afbd66d293e8a7509943d5cd6f851f59923a61a186e80000000000000000000000000000000000000000000000000000000000000060a063498e8db2e75e0a193de89ad2947111d677c9501e75c34a64fcee8fe5a7c7607929fc6bce943d64f1039e1d1f325f02d1e5d71f86ca976c9ab79d19f0fd0e530a5210fbe131087ba1f1b3c92abc4a0dd7c8a47c3c276fac3e09bca964fd74", + nonce: 4658, + r: + 105_551_060_165_173_654_536_466_245_809_705_255_348_773_503_447_188_823_324_699_103_004_494_755_354_009, + s: + 6_578_424_718_200_222_268_891_012_570_118_685_130_111_416_504_340_507_122_286_266_818_507_627_932_617, + to_address_hash: "0xf003a84d6890202663c0fd80954e836fcf21e004", + transaction_index: 0, + v: 28, + value: 3_200_000_000_000_000 + } + ]} = Geth.fetch_pending_transactions(json_rpc_named_arguments) end end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs index ad7780181d..a8a8fcdfd2 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs @@ -2,4 +2,14 @@ defmodule EthereumJSONRPC.TransactionTest do use ExUnit.Case, async: true doctest EthereumJSONRPC.Transaction + + alias EthereumJSONRPC.Transaction + + describe "to_elixir/1" do + test "skips unsupported keys" do + map = %{"key" => "value", "key1" => "value1"} + + assert %{nil: nil} = Transaction.to_elixir(map) + end + end end 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..e6ffcbd9c5 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -232,7 +232,7 @@ defmodule Explorer.Chain do address_hash |> address_to_transactions_without_rewards(paging_options, options) - |> Enum.concat(Task.await(rewards_task)) + |> Enum.concat(Task.await(rewards_task, :timer.seconds(20))) |> Enum.sort_by(fn item -> case item do {%Reward{} = emission_reward, _} -> @@ -273,7 +273,7 @@ defmodule Explorer.Chain do from(log in Log, inner_join: transaction in assoc(log, :transaction), order_by: [desc: transaction.block_number, desc: transaction.index], - preload: [:transaction], + preload: [:transaction, transaction: [to_address: :smart_contract]], where: transaction.block_number < ^block_number, or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index, or_where: @@ -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( @@ -1807,7 +1818,7 @@ defmodule Explorer.Chain do Repo.one!(query) end - def last_block_status do + def last_db_block_status do query = from(block in Block, select: {block.number, block.timestamp}, @@ -1816,22 +1827,39 @@ defmodule Explorer.Chain do limit: 1 ) - case Repo.one(query) do - nil -> - {:error, :no_blocks} + query + |> Repo.one() + |> block_status() + end - {number, timestamp} -> - now = DateTime.utc_now() - last_block_period = DateTime.diff(now, timestamp, :millisecond) + def last_cache_block_status do + [ + paging_options: %PagingOptions{page_size: 1} + ] + |> list_blocks() + |> List.last() + |> case do + %{timestamp: timestamp, number: number} -> + block_status({number, timestamp}) - if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do - {:error, number, timestamp} - else - {:ok, number, timestamp} - end + _ -> + block_status(nil) end end + defp block_status({number, timestamp}) do + now = DateTime.utc_now() + last_block_period = DateTime.diff(now, timestamp, :millisecond) + + if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do + {:error, number, timestamp} + else + {:ok, number, timestamp} + end + end + + defp block_status(nil), do: {:error, :no_blocks} + @doc """ Calculates the ranges of missing consensus blocks in `range`. @@ -2209,14 +2237,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 +2268,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 +2296,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) @@ -2448,9 +2464,10 @@ defmodule Explorer.Chain do |> Multi.run(:set_address_verified, &set_address_verified/2) |> Repo.transaction() - with {:ok, %{smart_contract: smart_contract}} <- insert_result do - {:ok, smart_contract} - else + case insert_result do + {:ok, %{smart_contract: smart_contract}} -> + {:ok, smart_contract} + {:error, :smart_contract, changeset, _} -> {:error, changeset} @@ -2510,16 +2527,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, @@ -2585,7 +2602,11 @@ defmodule Explorer.Chain do defp page_addresses(query, %PagingOptions{key: nil}), do: query defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do - where(query, [address], address.fetched_coin_balance <= ^coin_balance and address.hash > ^hash) + from(address in query, + where: + (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or + address.fetched_coin_balance < ^coin_balance + ) end defp page_blocks(query, %PagingOptions{key: nil}), do: query @@ -2920,9 +2941,10 @@ defmodule Explorer.Chain do ) |> Repo.transaction() - with {:ok, %{token: token}} <- insert_result do - {:ok, token} - else + case insert_result do + {:ok, %{token: token}} -> + {:ok, token} + {:error, :token, changeset, _} -> {:error, changeset} end @@ -3276,8 +3298,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 +3320,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/hash/address.ex b/apps/explorer/lib/explorer/chain/hash/address.ex index fefdf8743c..4cb1ba751f 100644 --- a/apps/explorer/lib/explorer/chain/hash/address.ex +++ b/apps/explorer/lib/explorer/chain/hash/address.ex @@ -166,7 +166,7 @@ defmodule Explorer.Chain.Hash.Address do iex> Explorer.Chain.Hash.Address.validate("0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232H") {:error, :invalid_characters} """ - @spec validate(String.t()) :: {:ok, String.t()} | {:error, :invalid_length | :invalid_characters, :invalid_checksum} + @spec validate(String.t()) :: {:ok, String.t()} | {:error, :invalid_length | :invalid_characters | :invalid_checksum} def validate("0x" <> hash) do with {:length, true} <- {:length, String.length(hash) == 40}, {:hex, true} <- {:hex, is_hex?(hash)}, diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 5e2e4e32f5..50529e8ce8 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -73,10 +73,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) + final_changes_list = reject_pending_transactions(ordered_changes_list, repo) + {:ok, internal_transactions} = Import.insert_changes_list( repo, - ordered_changes_list, + final_changes_list, conflict_target: [:transaction_hash, :index], for: InternalTransaction, on_conflict: on_conflict, @@ -156,6 +158,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do from( t in Transaction, where: t.hash in ^ordered_transaction_hashes, + where: not is_nil(t.block_hash), update: [ set: [ internal_transactions_indexed_at: ^timestamps.updated_at, @@ -180,10 +183,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do ] ) - transaction_count = Enum.count(ordered_transaction_hashes) - try do - {^transaction_count, result} = repo.update_all(query, [], timeout: timeout) + {_transaction_count, result} = repo.update_all(query, [], timeout: timeout) {:ok, result} rescue @@ -191,4 +192,23 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:error, %{exception: postgrex_error, transaction_hashes: ordered_transaction_hashes}} end end + + defp reject_pending_transactions(ordered_changes_list, repo) do + transaction_hashes = + ordered_changes_list + |> Enum.map(& &1.transaction_hash) + |> Enum.dedup() + + query = + from(t in Transaction, + where: t.hash in ^transaction_hashes, + where: is_nil(t.block_hash), + select: t.hash + ) + + pending_transactions = repo.all(query) + + ordered_changes_list + |> Enum.reject(fn %{transaction_hash: hash} -> Enum.member?(pending_transactions, hash) end) + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 1231677a57..77f7d15da2 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -392,7 +392,7 @@ defmodule Explorer.Chain.InternalTransaction do @create_required_fields ~w(from_address_hash gas index init trace_address transaction_hash value)a @create_allowed_fields @create_optional_fields ++ @create_required_fields - defp type_changeset(changeset, attrs, :create) do + defp type_changeset(changeset, attrs, type) when type in [:create, :create2] do changeset |> cast(attrs, @create_allowed_fields) |> validate_required(@create_required_fields) @@ -537,7 +537,7 @@ defmodule Explorer.Chain.InternalTransaction do |> put_raw_call_error_or_result(transaction) end - defp internal_transaction_to_raw(%{type: :create} = transaction) do + defp internal_transaction_to_raw(%{type: type} = transaction) when type in [:create, :create2] do %{ from_address_hash: from_address_hash, gas: gas, @@ -549,7 +549,7 @@ defmodule Explorer.Chain.InternalTransaction do action = %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value} %{ - "type" => "create", + "type" => Atom.to_string(type), "action" => Action.to_raw(action), "traceAddress" => trace_address } diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex index 4133dcf45f..0a13587afe 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex @@ -11,7 +11,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do * `:reward` * `:selfdestruct` """ - @type t :: :call | :create | :reward | :selfdestruct + @type t :: :call | :create | :create2 | :reward | :selfdestruct @doc """ Casts `term` to `t:t/0` @@ -22,6 +22,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do {:ok, :call} iex> Explorer.Chain.InternalTransaction.Type.cast(:create) {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.cast(:create2) + {:ok, :create2} iex> Explorer.Chain.InternalTransaction.Type.cast(:reward) {:ok, :reward} iex> Explorer.Chain.InternalTransaction.Type.cast(:selfdestruct) @@ -33,6 +35,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do {:ok, :call} iex> Explorer.Chain.InternalTransaction.Type.cast("create") {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.cast("create2") + {:ok, :create2} iex> Explorer.Chain.InternalTransaction.Type.cast("reward") {:ok, :reward} iex> Explorer.Chain.InternalTransaction.Type.cast("selfdestruct") @@ -53,9 +57,10 @@ defmodule Explorer.Chain.InternalTransaction.Type do """ @impl Ecto.Type @spec cast(term()) :: {:ok, t()} | :error - def cast(t) when t in ~w(call create selfdestruct reward)a, do: {:ok, t} + def cast(t) when t in ~w(call create create2 selfdestruct reward)a, do: {:ok, t} def cast("call"), do: {:ok, :call} def cast("create"), do: {:ok, :create} + def cast("create2"), do: {:ok, :create2} def cast("reward"), do: {:ok, :reward} def cast("selfdestruct"), do: {:ok, :selfdestruct} def cast(_), do: :error @@ -67,6 +72,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do {:ok, "call"} iex> Explorer.Chain.InternalTransaction.Type.dump(:create) {:ok, "create"} + iex> Explorer.Chain.InternalTransaction.Type.dump(:create2) + {:ok, "create2"} iex> Explorer.Chain.InternalTransaction.Type.dump(:reward) {:ok, "reward"} iex> Explorer.Chain.InternalTransaction.Type.dump(:selfdestruct) @@ -87,6 +94,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do @spec dump(term()) :: {:ok, String.t()} | :error def dump(:call), do: {:ok, "call"} def dump(:create), do: {:ok, "create"} + def dump(:create2), do: {:ok, "create2"} def dump(:reward), do: {:ok, "reward"} def dump(:selfdestruct), do: {:ok, "selfdestruct"} def dump(_), do: :error @@ -98,6 +106,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do {:ok, :call} iex> Explorer.Chain.InternalTransaction.Type.load("create") {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.load("create2") + {:ok, :create2} iex> Explorer.Chain.InternalTransaction.Type.load("reward") {:ok, :reward} iex> Explorer.Chain.InternalTransaction.Type.load("selfdestruct") @@ -118,6 +128,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do @spec load(term()) :: {:ok, t()} | :error def load("call"), do: {:ok, :call} def load("create"), do: {:ok, :create} + def load("create2"), do: {:ok, :create2} def load("reward"), do: {:ok, :reward} def load("selfdestruct"), do: {:ok, :selfdestruct} # deprecated 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/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index c1c434aa44..78483632a7 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -31,21 +31,25 @@ defmodule Explorer.Chain.Wei do @impl Ecto.Type def cast("0x" <> hex_wei) do - with {int_wei, ""} <- Integer.parse(hex_wei, 16) do - decimal = Decimal.new(int_wei) - {:ok, %__MODULE__{value: decimal}} - else - _ -> :error + case Integer.parse(hex_wei, 16) do + {int_wei, ""} -> + decimal = Decimal.new(int_wei) + {:ok, %__MODULE__{value: decimal}} + + _ -> + :error end end @impl Ecto.Type def cast(string_wei) when is_binary(string_wei) do - with {int_wei, ""} <- Integer.parse(string_wei) do - decimal = Decimal.new(int_wei) - {:ok, %__MODULE__{value: decimal}} - else - _ -> :error + case Integer.parse(string_wei) do + {int_wei, ""} -> + decimal = Decimal.new(int_wei) + {:ok, %__MODULE__{value: decimal}} + + _ -> + :error end end 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/mix.exs b/apps/explorer/mix.exs index 4956363cc4..4d73894d5e 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -68,19 +68,19 @@ defmodule Explorer.Mixfile do {:bypass, "~> 1.0", only: :test}, {:briefly, "~> 0.4", github: "CargoSense/briefly"}, {:comeonin, "~> 4.0"}, - {:credo, "1.0.0", only: :test, runtime: false}, + {:credo, "~> 1.1", only: :test, runtime: false}, # For Absinthe to load data in batches {:dataloader, "~> 1.0.0"}, {:decimal, "~> 1.0"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, # `override: true` for `ex_machina` compatibility - {:ecto, "~> 3.0", override: true}, + {:ecto, "~> 3.1", override: true}, # Storing blockchain data and derived data in PostgreSQL. - {:ecto_sql, "~> 3.0"}, + {:ecto_sql, "~> 3.1"}, # JSONRPC access to query smart contracts {:ethereum_jsonrpc, in_umbrella: true}, # Data factory for testing - {:ex_machina, "~> 2.1", only: [:test]}, + {:ex_machina, "~> 2.3", only: [:test]}, # Code coverage {:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"}, {:exvcr, "~> 0.10", only: :test}, @@ -98,12 +98,7 @@ defmodule Explorer.Mixfile do # For compatibility with `prometheus_process_collector`, which hasn't been updated yet {:prometheus, "~> 4.0", override: true}, # Prometheus metrics for query duration - { - :prometheus_ecto, - "~> 1.3", - # Ecto 3.0 compatibility - github: "deadtrickster/prometheus-ecto", ref: "650a403183f6a2fb6b682d7fbcba8bf9d24fe1e4" - }, + {:prometheus_ecto, "~> 1.4.3"}, # bypass optional dependency {:plug_cowboy, "~> 2.0", only: [:dev, :test]}, {:que, "~> 0.10.1"}, @@ -115,9 +110,9 @@ defmodule Explorer.Mixfile do # `:spandex` tracing of `:ecto` {:spandex_ecto, "~> 0.4.0"}, # Attach `:prometheus_ecto` to `:ecto` - {:telemetry, "~> 0.3.0"}, + {:telemetry, "~> 0.4.0"}, # `Timex.Duration` for `Explorer.Counters.AverageBlockTime.average_block_time/0` - {:timex, "~> 3.4"}, + {:timex, "~> 3.6"}, {:con_cache, "~> 0.13"} ] end diff --git a/apps/explorer/priv/repo/migrations/20181108205650_additional_internal_transaction_constraints.exs b/apps/explorer/priv/repo/migrations/20181108205650_additional_internal_transaction_constraints.exs index de554e5acf..5caa150eaa 100644 --- a/apps/explorer/priv/repo/migrations/20181108205650_additional_internal_transaction_constraints.exs +++ b/apps/explorer/priv/repo/migrations/20181108205650_additional_internal_transaction_constraints.exs @@ -7,6 +7,9 @@ defmodule Explorer.Repo.Migrations.AdditionalInternalTransactionConstraints do mix ecto.migrate psql -d $DATABASE -a -f priv/repo/migrations/scripts/20181108205650_additional_internal_transaction_constraints.sql ``` + + NOTE: you may want to consider using `apps/explorer/priv/repo/migrations/scripts/20181108205650_large_additional_internal_transaction_constraints.sql` + instead if you are dealing with a very large number of transactions/internal_transactions. """ use Ecto.Migration diff --git a/apps/explorer/priv/repo/migrations/scripts/20181108205650_large_additional_internal_transaction_constraints.sql b/apps/explorer/priv/repo/migrations/scripts/20181108205650_large_additional_internal_transaction_constraints.sql new file mode 100644 index 0000000000..cb82ce8847 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/scripts/20181108205650_large_additional_internal_transaction_constraints.sql @@ -0,0 +1,75 @@ +-- This script is a reimplementation of `20181108205650_additional_internal_transaction_constraints.sql` +-- that is meant to be executed on DBs where the number of transactions and/or +-- internal_transactions is very large. +-- To check the progress it is advised to run this in a `tmux` session or save +-- the output to a file. +-- IMPORTANT NOTE: after making all the corrections needed the script will NOT +-- run the constraint validations because this may be a very long and taxing +-- operation. To validate the constraint one can run, after the script fininshed: + +-- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_call_type; +-- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_input; +-- ALTER TABLE internal_transactions VALIDATE CONSTRAINT create_has_init; + +DO $$ +DECLARE + batch_size integer := 10000; -- HOW MANY ITEMS WILL BE UPDATED AT A TIME + last_transaction_hash bytea; -- WILL CHECK ONLY TRANSACTIONS FOLLOWING THIS HASH (DESC) + last_fetched_batch_size integer; +BEGIN + RAISE NOTICE 'STARTING SCRIPT'; + CREATE TEMP TABLE transactions_with_deprecated_internal_transactions(hash bytea NOT NULL); + + LOOP + RAISE NOTICE 'Fetching new batch of % transactions to correct', batch_size; + + INSERT INTO transactions_with_deprecated_internal_transactions + SELECT DISTINCT transaction_hash + FROM internal_transactions + WHERE + (last_transaction_hash IS NULL OR transaction_hash < last_transaction_hash) AND + -- call_has_call_type CONSTRAINT + ((type = 'call' AND call_type IS NULL) OR + -- call_has_input CONSTRAINT + (type = 'call' AND input IS NULL) OR + -- create_has_init CONSTRAINT + (type = 'create' AND init is NULL)) + ORDER BY transaction_hash DESC LIMIT batch_size; + + SELECT INTO last_fetched_batch_size count(*) FROM transactions_with_deprecated_internal_transactions; + + RAISE NOTICE 'Batch of % transactions was fetched, starting their deprecation', last_fetched_batch_size; + + -- UPDATE TRANSACTIONS + UPDATE transactions + SET internal_transactions_indexed_at = NULL, + error = NULL + FROM transactions_with_deprecated_internal_transactions + WHERE transactions.hash = transactions_with_deprecated_internal_transactions.hash; + + -- REMOVE THE DEPRECATED internal_transactions + DELETE FROM internal_transactions + USING transactions_with_deprecated_internal_transactions + WHERE internal_transactions.transaction_hash = transactions_with_deprecated_internal_transactions.hash; + + -- COMMIT THE BATCH UPDATES + CHECKPOINT; + + -- UPDATE last_transaction_hash TO KEEP TRACK OF ROWS ALREADY CHECKED + SELECT INTO last_transaction_hash hash + FROM transactions_with_deprecated_internal_transactions + ORDER BY hash ASC LIMIT 1; + + RAISE NOTICE 'Last batch completed, last transaction hash: %', last_transaction_hash; + + -- CLEAR THE TEMP TABLE + DELETE FROM transactions_with_deprecated_internal_transactions; + + -- EXIT IF ALL internal_transactions HAVE BEEN CHECKED ALREADY + EXIT WHEN last_fetched_batch_size != batch_size; + END LOOP; + + RAISE NOTICE 'SCRIPT FINISHED, all affected transactions have been deprecated'; + + DROP TABLE transactions_with_deprecated_internal_transactions; +END $$; diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index f5c080991e..66d62ae940 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -2,7 +2,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do use Explorer.DataCase alias Ecto.Multi - alias Explorer.Chain.{Data, Wei, Transaction} + alias Explorer.Chain.{Data, Wei, Transaction, InternalTransaction} alias Explorer.Chain.Import.Runner.InternalTransactions describe "run/1" do @@ -20,6 +20,28 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert :error == Repo.get(Transaction, transaction.hash).status end + + test "pending transactions don't get updated not its internal_transactions inserted" do + transaction = insert(:transaction) |> with_block(status: :ok) + pending = insert(:transaction) + + assert :ok == transaction.status + assert is_nil(pending.block_hash) + + index = 0 + + transaction_changes = make_internal_transaction_changes(transaction.hash, index, nil) + pending_changes = make_internal_transaction_changes(pending.hash, index, nil) + + assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes]) + + assert %InternalTransaction{} = + Repo.one(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash)) + + assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() + + assert is_nil(Repo.get(Transaction, pending.hash).block_hash) + end end defp run_internal_transactions(changes_list) when is_list(changes_list) do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 38e61dee7d..234252dbe4 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -50,21 +50,35 @@ defmodule Explorer.ChainTest do end end - describe "last_block_status/0" do + describe "last_db_block_status/0" do test "return no_blocks errors if db is empty" do - assert {:error, :no_blocks} = Chain.last_block_status() + assert {:error, :no_blocks} = Chain.last_db_block_status() end test "returns {:ok, last_block_period} if block is in healthy period" do insert(:block, consensus: true) - assert {:ok, _, _} = Chain.last_block_status() + assert {:ok, _, _} = Chain.last_db_block_status() end test "return {:ok, last_block_period} if block is not in healthy period" do insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) - assert {:error, _, _} = Chain.last_block_status() + assert {:error, _, _} = Chain.last_db_block_status() + end + end + + describe "last_cache_block_status/0" do + test "returns success if cache is not stale" do + insert(:block, consensus: true) + + assert {:ok, _, _} = Chain.last_cache_block_status() + end + + test "return error if cache is stale" do + insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) + + assert {:error, _, _} = Chain.last_cache_block_status() end end @@ -630,7 +644,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 +653,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 +671,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 +697,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 +707,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 +716,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 +2104,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 +2131,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 +2165,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 +2175,7 @@ defmodule Explorer.ChainTest do } ] = Chain.transaction_to_internal_transactions( - transaction, + transaction.hash, necessity_by_association: %{ :from_address => :optional, :to_address => :optional, @@ -2183,7 +2197,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 +2216,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 +2236,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 +2257,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 +2285,7 @@ defmodule Explorer.ChainTest do ) result = - transaction + transaction.hash |> Chain.transaction_to_internal_transactions() |> Enum.map(&{&1.transaction_hash, &1.index}) @@ -2301,17 +2315,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 +2335,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 +2346,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 +2363,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 +2378,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 +2390,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 +2398,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 +2411,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 +2424,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 +2436,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 +2494,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 +2547,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/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 4e84e9bcfe..9ab951d668 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -35,7 +35,8 @@ config :indexer, String.to_integer(System.get_env("TOKEN_METADATA_UPDATE_INTERVAL") || "#{2 * 24 * 60 * 60}"), # bytes memory_limit: 1 <<< 30, - first_block: System.get_env("FIRST_BLOCK") || "0" + first_block: System.get_env("FIRST_BLOCK") || "0", + last_block: System.get_env("LAST_BLOCK") || "" # config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true # config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true diff --git a/apps/indexer/lib/indexer/block/catchup/bound_interval_supervisor.ex b/apps/indexer/lib/indexer/block/catchup/bound_interval_supervisor.ex index f1b216aa2b..b128ad4ca0 100644 --- a/apps/indexer/lib/indexer/block/catchup/bound_interval_supervisor.ex +++ b/apps/indexer/lib/indexer/block/catchup/bound_interval_supervisor.ex @@ -185,7 +185,12 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisor do def handle_info( {ref, - %{first_block_number: first_block_number, missing_block_count: missing_block_count, shrunk: false = shrunk}}, + %{ + first_block_number: first_block_number, + last_block_number: last_block_number, + missing_block_count: missing_block_count, + shrunk: false = shrunk + }}, %__MODULE__{ bound_interval: bound_interval, task: %Task{ref: ref} @@ -197,7 +202,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisor do 0 -> Logger.info("Index already caught up.", first_block_number: first_block_number, - last_block_number: 0, + last_block_number: last_block_number, missing_block_count: 0, shrunk: shrunk ) diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index bb15ebcd2d..f228551a37 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -72,7 +72,14 @@ defmodule Indexer.Block.Catchup.Fetcher do ) do Logger.metadata(fetcher: :block_catchup) - {:ok, latest_block_number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) + {:ok, latest_block_number} = + case latest_block() do + nil -> + EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) + + number -> + {:ok, number} + end case latest_block_number do # let realtime indexer get the genesis block @@ -116,7 +123,7 @@ defmodule Indexer.Block.Catchup.Fetcher do Shrinkable.shrunk?(sequence) end - %{first_block_number: first, missing_block_count: missing_block_count, shrunk: shrunk} + %{first_block_number: first, last_block_number: last, missing_block_count: missing_block_count, shrunk: shrunk} end end @@ -337,4 +344,13 @@ defmodule Indexer.Block.Catchup.Fetcher do _ -> 0 end end + + defp latest_block do + string_value = Application.get_env(:indexer, :last_block) + + case Integer.parse(string_value) do + {integer, ""} -> integer + _ -> nil + end + end end diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 3b9746b838..7d93bbf64f 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -152,12 +152,6 @@ defmodule Indexer.Fetcher.InternalTransaction do unique_entries = unique_entries(entries, variant) - internal_transactions_indexed_at_blocks = - case variant do - EthereumJSONRPC.Parity -> Enum.map(unique_entries, &block_params/1) - _ -> [] - end - unique_entries_count = Enum.count(unique_entries) Logger.metadata(count: unique_entries_count) @@ -176,47 +170,7 @@ defmodule Indexer.Fetcher.InternalTransaction do end |> case do {:ok, internal_transactions_params} -> - internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) - - addresses_params = - Addresses.extract_addresses(%{ - internal_transactions: internal_transactions_params_without_failed_creations - }) - - address_hash_to_block_number = - Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} -> - {hash, block_number} - end) - - with {:ok, imported} <- - Chain.import(%{ - addresses: %{params: addresses_params}, - internal_transactions: %{params: internal_transactions_params_without_failed_creations}, - internal_transactions_indexed_at_blocks: %{ - params: internal_transactions_indexed_at_blocks, - with: :number_only_changeset - }, - timeout: :infinity - }) do - async_import_coin_balances(imported, %{ - address_hash_to_fetched_balance_block_number: address_hash_to_block_number - }) - else - {:error, step, reason, _changes_so_far} -> - Logger.error( - fn -> - [ - "failed to import internal transactions for transactions: ", - inspect(reason) - ] - end, - step: step, - error_count: unique_entries_count - ) - - # re-queue the de-duped entries - {:retry, unique_entries} - end + import_internal_transaction(internal_transactions_params, json_rpc_named_arguments, unique_entries) {:error, reason} -> Logger.error(fn -> ["failed to fetch internal transactions for transactions: ", inspect(reason)] end, @@ -231,6 +185,60 @@ defmodule Indexer.Fetcher.InternalTransaction do end end + defp import_internal_transaction(internal_transactions_params, json_rpc_named_arguments, unique_entries) do + internal_transactions_indexed_at_blocks = + case Keyword.fetch!(json_rpc_named_arguments, :variant) do + EthereumJSONRPC.Parity -> Enum.map(unique_entries, &block_params/1) + _ -> [] + end + + unique_entries_count = Enum.count(unique_entries) + internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) + + addresses_params = + Addresses.extract_addresses(%{ + internal_transactions: internal_transactions_params_without_failed_creations + }) + + address_hash_to_block_number = + Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} -> + {hash, block_number} + end) + + imports = + Chain.import(%{ + addresses: %{params: addresses_params}, + internal_transactions: %{params: internal_transactions_params_without_failed_creations}, + internal_transactions_indexed_at_blocks: %{ + params: internal_transactions_indexed_at_blocks, + with: :number_only_changeset + }, + timeout: :infinity + }) + + case imports do + {:ok, imported} -> + async_import_coin_balances(imported, %{ + address_hash_to_fetched_balance_block_number: address_hash_to_block_number + }) + + {:error, step, reason, _changes_so_far} -> + Logger.error( + fn -> + [ + "failed to import internal transactions for transactions: ", + inspect(reason) + ] + end, + step: step, + error_count: unique_entries_count + ) + + # re-queue the de-duped entries + {:retry, unique_entries} + end + end + defp unique_entries(entries, EthereumJSONRPC.Parity), do: Enum.uniq(entries) # Protection and improved reporting for https://github.com/poanetwork/blockscout/issues/289 diff --git a/docs/env-variables.md b/docs/env-variables.md index d6dd56d7af..edb437ce2f 100644 --- a/docs/env-variables.md +++ b/docs/env-variables.md @@ -48,6 +48,7 @@ $ export NETWORK=POA | `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ | | `GRAPHIQL _TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | v1.3.4+ | | `FIRST_BLOCK` | | The block number, where indexing begins from. | 0 | v1.3.8+ | +| `LAST_BLOCK` | | The block number, where indexing stops. | (empty) | master | | `TXS_COUNT_CACHE_PERIOD` | | Interval in seconds to restart the task, which calculates the total txs count. | 60 * 60 * 2 | v1.3.9+ | | `ADDRESS_WITH_BALANCES`
`_UPDATE_INTERVAL`| | Interval in seconds to restart the task, which calculates addresses with balances. | 30 * 60 | v1.3.9+ | | `LINK_TO_OTHER_EXPLORERS` | | true/false. If true, links to other explorers are added in the footer | (empty) | v1.3.0+ | diff --git a/mix.lock b/mix.lock index 5ff4c0f8d0..92fd054cb6 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,8 @@ "abi": {:hex, :abi, "0.1.12", "87ae04cb09e2308db7b3c350584dc3934de0e308f6a056ba82be5756b081a1ca", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], []}, "absinthe": {:hex, :absinthe, "1.4.14", "fef224a6aac63d6eaafbc0cb96040a8abcd572275b9b4db69d46329acdcae7c7", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "absinthe_phoenix": {:git, "https://github.com/ayrat555/absinthe_phoenix.git", "4104c7213c328c3698b52710d49cd6d85449305c", [branch: "master"]}, - "absinthe_plug": {:git, "https://github.com/ayrat555/absinthe_plug.git", "e74da0a6e004fe4126885b5f5f60a3e72abd70bb", [branch: "ab-allow-to-set-default-query"]}, + "absinthe_phoenix": {:git, "https://github.com/ayrat555/absinthe_phoenix.git", "0f5127844a9e4e1c5fecb1fcee225894a2af6336", [branch: "master"]}, + "absinthe_plug": {:git, "https://github.com/ayrat555/absinthe_plug.git", "cbe1c170e11e60b3b0146b925a1ce6ec562840ce", [branch: "ab-enable-default-query"]}, "absinthe_relay": {:hex, :absinthe_relay, "1.4.6", "ec0e2288994b388556247cf9601245abec785cdf206d6e844f2992d29de21624", [:mix], [{:absinthe, "~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "accept": {:hex, :accept, "0.3.3", "548ebb6fb2e8b0d170e75bb6123aea6ceecb0189bb1231eeadf52eac08384a97", [:rebar3], [], "hexpm"}, "artificery": {:hex, :artificery, "0.2.6", "f602909757263f7897130cbd006b0e40514a541b148d366ad65b89236b93497a", [:mix], [], "hexpm"}, @@ -14,7 +14,8 @@ "briefly": {:git, "https://github.com/CargoSense/briefly.git", "2526e9674a4e6996137e066a1295ea60962712b8", []}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "cldr_utils": {:hex, :cldr_utils, "2.3.0", "e7e8b5ad7494a929c1b620cc489c3aa3f6e7e5299584c1a934bbdb56d1a53c70", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], []}, "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"}, "con_cache": {:hex, :con_cache, "0.13.1", "047e097ab2a8c6876e12d0c29e29a86d487b592df97b98e3e2abedad574e215d", [:mix], [], "hexpm"}, @@ -22,25 +23,27 @@ "cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"}, - "credo": {:hex, :credo, "1.0.0", "aaa40fdd0543a0cf8080e8c5949d8c25f0a24e4fc8c1d83d06c388f5e5e0ea42", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "1.1.2", "02b6422f3e659eb74b05aca3c20c1d8da0119a05ee82577a82e6c2938bf29f81", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "csv": {:hex, :csv, "2.1.1", "a4c1a7c30d2151b6e4976cb2f52c0a1d49ec965afb737ed84a684bc4284d1627", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, optional: false]}]}, "dataloader": {:hex, :dataloader, "1.0.6", "fb724d6d3fb6acb87d27e3b32dea3a307936ad2d245faf9cf5221d1323d6a4ba", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.0.3", "b4e8aa43c100e16f122ccd6798cd51c48c79fd391c39d411f42b3cd765daccb0", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "decorator": {:hex, :decorator, "1.3.0", "6203dbd6e4e519a21a079e2a74e50fddaf03e80be22711b92eb4cd410173abea", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], []}, "distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.0.4", "e7a0feb0b2484b90981c56d5cd03c52122c1c31ded0b95ed213b7c5c07ae6737", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "ecto": {:hex, :ecto, "3.1.7", "fa21d06ef56cdc2fdaa62574e8c3ba34a2751d44ea34c30bc65f0728421043e5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ecto_sql": {:hex, :ecto_sql, "3.1.6", "1e80e30d16138a729c717f73dcb938590bcdb3a4502f3012414d0cbb261045d8", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0 or ~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, "ex_abi": {:hex, :ex_abi, "0.1.18", "19db9bffdd201edbdff97d7dd5849291218b17beda045c1b76bff5248964f37d", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_cldr": {:hex, :ex_cldr, "1.3.2", "8f4a00c99d1c537b8e8db7e7903f4bd78d82a7289502d080f70365392b13921b", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, optional: true]}]}, - "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.2.0", "ef27299922da913ffad1ed296cacf28b6452fc1243b77301dc17c03276c6ee34", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, optional: false]}, {:ex_cldr, "~> 1.3", [hex: :ex_cldr, optional: false]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, optional: false]}]}, - "ex_cldr_units": {:hex, :ex_cldr_units, "1.1.1", "b3c7256709bdeb3740a5f64ce2bce659eb9cf4cc1afb4cf94aba033b4a18bc5f", [:mix], [{:ex_cldr, "~> 1.0", [hex: :ex_cldr, optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, optional: false]}]}, + "ex_cldr": {:hex, :ex_cldr, "2.7.2", "d79a1af6ed12630a15175d2b88d4381b22db5d6f835c5da8de132f0cf712b233", [:mix], [{:cldr_utils, "~> 2.1", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.3.0", "bffae489416b8b05d4683403263f5d62aae17de70c24ff915a533541fea514de", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.2.0", "b99f8752d098fc6ba5f083bbd0b25d0d01e36b0042155cf6abd5f205306ba849", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.6.4", "5b1ac8451f889576bb29dee70412de1170974298727ab944aa4d17e91bdd3472", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.3", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_cldr_units": {:hex, :ex_cldr_units, "2.5.1", "0e65067a22a7c5146266c313d6333c2700868c32aa6d536f47c6c0d84aac3ac1", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.2", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_machina": {:hex, :ex_machina, "2.2.2", "d84217a6fb7840ff771d2561b8aa6d74a0d8968e4b10ecc0d7e9890dc8fb1c6a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, "ex_rlp": {:hex, :ex_rlp, "0.5.2", "7f4ce7bd55e543c054ce6d49629b01e9833c3462e3d547952be89865f39f2c58", [:mix], [], "hexpm"}, "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], []}, @@ -53,10 +56,10 @@ "flow": {:hex, :flow, "0.14.3", "0d92991fe53035894d24aa8dec10dcfccf0ae00c4ed436ace3efa9813a646902", [:mix], [{:gen_stage, "~> 0.14.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], []}, "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], []}, "junit_formatter": {:hex, :junit_formatter, "3.0.0", "13950d944dbd295da7d8cc4798b8faee808a8bb9b637c88069954eac078ac9da", [:mix], [], "hexpm"}, @@ -70,7 +73,7 @@ "memento": {:hex, :memento, "0.3.1", "b2909390820550d8b90b68ec96f9e15ff8a45a28b6f97fa4a62ef50e87c2f9d9", [:mix], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, @@ -80,21 +83,21 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], []}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.4", "4af0603d8d41ca638e70f74d6defff331e4db106dd85f75f125579ca27bd8b64", [:mix], [{:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.15.0", "dd5349161019caeea93efa42f9b22f9d79995c3a86bdffb796427b4c9863b0f0", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "prometheus": {:hex, :prometheus, "4.2.0", "06c58bfdfe28d3168b926da614cb9a6d39593deebde648a5480e32dfa3c370e9", [:mix, :rebar3], [], "hexpm"}, - "prometheus_ecto": {:git, "https://github.com/deadtrickster/prometheus-ecto.git", "650a403183f6a2fb6b682d7fbcba8bf9d24fe1e4", [ref: "650a403183f6a2fb6b682d7fbcba8bf9d24fe1e4"]}, + "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.3", "5d722263bb1f7a9b1d02554de42e61ea672b4e3c07c3f74e23ce35ab5e111cfa", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"}, @@ -108,11 +111,11 @@ "spandex_datadog": {:hex, :spandex_datadog, "0.4.0", "75113a73e843123074886a2e31994af07d6e0632749a8d97e9ca6157b120c287", [:mix], [{:msgpax, "~> 2.2.1", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 2.3", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"}, "spandex_ecto": {:hex, :spandex_ecto, "0.4.0", "deaeaddc11a35f1c551206c53d09bb93a0da5808dbef751430e465c8c7de01d3", [:mix], [{:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"}, "spandex_phoenix": {:hex, :spandex_phoenix, "0.3.1", "9cb9a4a9f2161f171d9df9afa1289a0d037abbbeecabae674f959b57f106f201", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"}, - "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, + "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, "wallaby": {:hex, :wallaby, "0.22.0", "e5d16bfa7ab23562c8a6e3b0a31445a2fd470ca622082a910114807ba823780d", [:mix], [{:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"}, "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"}, "wobserver": {:git, "https://github.com/poanetwork/wobserver.git", "13bcda30a87f4f0be1878920a79433ad831eefbe", [branch: "support-https"]}, diff --git a/rel/commands/clear_build.sh b/rel/commands/clear_build.sh index 5faca7ffeb..a07a11870e 100755 --- a/rel/commands/clear_build.sh +++ b/rel/commands/clear_build.sh @@ -2,11 +2,13 @@ rm -rf ./_build rm -rf ./deps -rm -rf ./logs/dev -rm -rf ./apps/explorer/node_modules -rm -rf ./apps/block_scout_web/assets/node_modules +logs=$(find . -not -path '*/\.*' -name "logs" -type d) +dev=$(find ${logs} -name "dev") +rm -rf {ls -la ${dev}} + +find . -name "node_modules" -type d -exec rm -rf '{}' + case "$1" in -f) rm -rf ./apps/block_scout_web/priv/static;; -esac \ No newline at end of file +esac