diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 200bce21f743..76d535c23168 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -5,11 +5,12 @@ :0: Unknown type 'Elixir.Address':t/0 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return lib/explorer/repo/prometheus_logger.ex:8 -lib/explorer/smart_contract/publisher_worker.ex:6 +lib/explorer/smart_contract/solidity/publisher_worker.ex:1 +lib/explorer/smart_contract/vyper/publisher_worker.ex:1 +lib/explorer/smart_contract/solidity/publisher_worker.ex:6 +lib/explorer/smart_contract/vyper/publisher_worker.ex:6 apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() -apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true' -apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true' lib/block_scout_web/router.ex:1 lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 @@ -18,11 +19,14 @@ lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'p lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!' lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21 lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22 -lib/explorer/smart_contract/reader.ex:344 +lib/explorer/smart_contract/reader.ex:440 lib/indexer/fetcher/token_total_supply_on_demand.ex:16 lib/explorer/exchange_rates/source.ex:110 lib/explorer/exchange_rates/source.ex:113 lib/explorer/smart_contract/verifier.ex:89 +lib/explorer/smart_contract/solidity/verifier.ex:89 +lib/block_scout_web/templates/address_contract/index.html.eex:156 +lib/block_scout_web/templates/address_contract/index.html.eex:199 lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no local return lib/explorer/staking/stake_snapshotting.ex:147 lib/explorer/third_party_integrations/sourcify.ex:70 @@ -33,4 +37,8 @@ lib/block_scout_web/views/transaction_view.ex:197 lib/block_scout_web/templates/address_contract/index.html.eex:168 lib/block_scout_web/templates/address_contract/index.html.eex:211 lib/explorer/smart_contract/reader.ex:379 +lib/explorer/smart_contract/reader.ex:478 +lib/indexer/buffered_task.ex:402 +lib/indexer/buffered_task.ex:451 +lib/indexer/memory/monitor.ex:161 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 5c8845a57e96..21d6fde2cd12 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -11,8 +11,8 @@ on: env: MIX_ENV: test - OTP_VERSION: '23.3.4.1' - ELIXIR_VERSION: '1.11.4' + OTP_VERSION: '24.1' + ELIXIR_VERSION: '1.12.3' jobs: build-and-cache: @@ -35,10 +35,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -90,15 +90,15 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} - + - name: Restore Mix Deps Cache uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -119,10 +119,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -142,10 +142,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +154,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" @@ -182,16 +182,16 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - run: | mix gettext.extract --merge | tee stdout.txt - ! grep "Wrote " stdout.txt + cat stdout.txt | grep '(0 new translations' | grep ' 0 removed' | grep ' 0 reworded' working-directory: "apps/block_scout_web" sobelow: name: Sobelow security analysis @@ -208,10 +208,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -236,10 +236,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -285,10 +285,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -347,10 +347,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -410,10 +410,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -484,10 +484,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -552,10 +552,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -627,4 +627,4 @@ jobs: - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 with: - files: artifacts/**/*.xml \ No newline at end of file + files: artifacts/**/*.xml diff --git a/.tool-versions b/.tool-versions index 0ea653fbabc0..1dad75002602 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.11.4-otp-23 -erlang 23.3.4.1 -nodejs 14.17.0 +elixir 1.12.3 +erlang 24.1 +nodejs 14.18.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e4de0c5da540..d6868dceff83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ ## Current ### Features +- [#4739](https://github.com/blockscout/blockscout/pull/4739) - Improve logs and inputs decoding +- [#4747](https://github.com/blockscout/blockscout/pull/4747) - Advanced CSV export +- [#4745](https://github.com/blockscout/blockscout/pull/4745) - Vyper contracts verification +- [#4699](https://github.com/blockscout/blockscout/pull/4699) - Address page facelifting +- [#4667](https://github.com/blockscout/blockscout/pull/4667) - Transaction Page: Add expand/collapse button for long contract method data +- [#4641](https://github.com/blockscout/blockscout/pull/4641), [#4733](https://github.com/blockscout/blockscout/pull/4733) - Improve Read Contract page logic +- [#4660](https://github.com/blockscout/blockscout/pull/4660) - Save Sourcify path instead of filename +- [#4656](https://github.com/blockscout/blockscout/pull/4656) - Open in Tenderly button +- [#4655](https://github.com/blockscout/blockscout/pull/4655), [#4676](https://github.com/blockscout/blockscout/pull/4676) - EIP-3091 support +- [#4621](https://github.com/blockscout/blockscout/pull/4621) - Add beacon contract address slot for proxy - [#4625](https://github.com/blockscout/blockscout/pull/4625) - Contract address page: Add implementation link to the overview of proxy contracts - [#4624](https://github.com/blockscout/blockscout/pull/4624) - Support HTML tags in alert message - [#4608](https://github.com/blockscout/blockscout/pull/4608), [#4622](https://github.com/blockscout/blockscout/pull/4622) - Block Details page: Improved style of transactions button @@ -10,7 +20,20 @@ - [#4579](https://github.com/blockscout/blockscout/pull/4579) - Write contract page: Resize inputs; Improve multiplier selector ### Fixes +- [#4751](https://github.com/blockscout/blockscout/pull/4751) - Change text and link for `trade STAKE` button +- [#4746](https://github.com/blockscout/blockscout/pull/4746) - Fix comparison of decimal value +- [#4711](https://github.com/blockscout/blockscout/pull/4711) - Add trimming to the contract functions inputs +- [#4729](https://github.com/blockscout/blockscout/pull/4729) - Fix bugs with fees in cases of txs with `gas price = 0` +- [#4725](https://github.com/blockscout/blockscout/pull/4725) - Fix hardcoded coin name on transaction's and block's page +- [#4717](https://github.com/blockscout/blockscout/pull/4717) - Contract verification fix: check only success creation tx - [#4713](https://github.com/blockscout/blockscout/pull/4713) - Search input field: sanitize input +- [#4703](https://github.com/blockscout/blockscout/pull/4703) - Block Details page: Fix pagination on the Transactions tab +- [#4686](https://github.com/blockscout/blockscout/pull/4686) - Block page: check gas limit value before division +- [#4678](https://github.com/blockscout/blockscout/pull/4678) - Internal transactions indexer: fix issue of some pending transactions never become confirmed +- [#4668](https://github.com/blockscout/blockscout/pull/4668) - Fix css for dark theme +- [#4654](https://github.com/blockscout/blockscout/pull/4654) - AddressView: Change `@burn_address` to string `0x0000000000000000000000000000000000000000` +- [#4626](https://github.com/blockscout/blockscout/pull/4626) - Refine view of popup for reverted tx +- [#4640](https://github.com/blockscout/blockscout/pull/4640) - Token page: fixes in mobile view - [#4612](https://github.com/blockscout/blockscout/pull/4612) - Hide error selector in the contract's functions list - [#4615](https://github.com/blockscout/blockscout/pull/4615) - Fix broken style for `View more transfers` button - [#4592](https://github.com/blockscout/blockscout/pull/4592) - Add `type` field for `receive` and `fallback` entities of a Smart Contract @@ -21,8 +44,16 @@ - [#4587](https://github.com/blockscout/blockscout/pull/4587) - Enable navbar menu on Search results page - [#4582](https://github.com/blockscout/blockscout/pull/4582) - Fix NaN input on write contract page - ### Chore +- [#4735](https://github.com/blockscout/blockscout/pull/4735) - Code clean up: Remove clauses for outdated ganache bugs +- [#4726](https://github.com/blockscout/blockscout/pull/4726) - Update chart.js +- [#4707](https://github.com/blockscout/blockscout/pull/4707) - Top navigation: Move Accounts tab to Tokens +- [#4704](https://github.com/blockscout/blockscout/pull/4704) - Update to Erlang/OTP 24 +- [#4682](https://github.com/blockscout/blockscout/pull/4682) - Update all possible outdated mix dependencies +- [#4663](https://github.com/blockscout/blockscout/pull/4663) - Migrate to Elixir 1.12.x +- [#4661](https://github.com/blockscout/blockscout/pull/4661) - Update NPM packages to resolve vulnerabilities +- [#4649](https://github.com/blockscout/blockscout/pull/4649) - 1559 Transaction Page: Convert Burnt Fee to ether and add price in USD +- [#4646](https://github.com/blockscout/blockscout/pull/4646) - Transaction page: Rename burned to burnt - [#4611](https://github.com/blockscout/blockscout/pull/4611) - Ability to hide miner in block views diff --git a/apps/block_scout_web/assets/__tests__/lib/currency.js b/apps/block_scout_web/assets/__tests__/lib/currency.js index e729c08f381d..407058f36220 100644 --- a/apps/block_scout_web/assets/__tests__/lib/currency.js +++ b/apps/block_scout_web/assets/__tests__/lib/currency.js @@ -4,7 +4,7 @@ test('formatUsdValue', () => { window.localized = { 'Less than': 'Less than' } - expect(formatUsdValue(0)).toEqual('$0.000000 USD') + expect(formatUsdValue(0)).toEqual('$0.00 USD') expect(formatUsdValue(0.0000001)).toEqual('Less than $0.000001 USD') expect(formatUsdValue(0.123456789)).toEqual('$0.123 USD') expect(formatUsdValue(0.12)).toEqual('$0.120 USD') diff --git a/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js new file mode 100644 index 000000000000..73b51b1f4655 --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js @@ -0,0 +1,180 @@ +import { prepareMethodArgs } from '../../../js/lib/smart_contract/common_helpers' +import $ from 'jquery' + +const oneFieldHTML = + '
' + +const twoFieldHTML = + '' + +test('prepare contract args | type: address[]*2', () => { + document.body.innerHTML = twoFieldHTML + + var inputs = [ + { + "type": "address[]", + "name": "arg1", + "internalType": "address[]" + }, + { + "type": "address[]", + "name": "arg2", + "internalType": "address[]" + } + ] + + document.getElementById('first').value = ' 0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001 ' + document.getElementById('second').value = ' 0x0000000000000000000000000000000000000002 , 0x0000000000000000000000000000000000000003 ' + const expectedValue = [ + [ + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000001' + ], + [ + '0x0000000000000000000000000000000000000002', + '0x0000000000000000000000000000000000000003' + ] + ] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: address', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "address", + "name": "arg1", + "internalType": "address" + } + ] + + document.getElementById('first').value = ' 0x000000000000000000 0000000000000000000000 ' + const expectedValue = ['0x0000000000000000000000000000000000000000'] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: string', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "string", + "name": "arg1", + "internalType": "string" + } + ] + + document.getElementById('first').value = ' 0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001 ' + const expectedValue = ['0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001'] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: string[]', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "string[]", + "name": "arg1", + "internalType": "string[]" + } + ] + + document.getElementById('first').value = ' " 0x0000000000000000000000000000000000000000 " , " 0x0000000000000000000000000000000000000001 " ' + const expectedValue = [['0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000001']] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: bool[]', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "bool[]", + "name": "arg1", + "internalType": "bool[]" + } + ] + + document.getElementById('first').value = ' true , false ' + const expectedValue = [[true, false]] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: bool', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "bool", + "name": "arg1", + "internalType": "bool" + } + ] + + document.getElementById('first').value = ' fals e ' + const expectedValue = [false] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + + +test('prepare contract args | type: uint256', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "uint256", + "name": "arg1", + "internalType": "uint256" + } + ] + + document.getElementById('first').value = ' 9 876 543 210 ' + const expectedValue = ['9876543210'] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) + +test('prepare contract args | type: uint256[]', () => { + document.body.innerHTML = oneFieldHTML + + var inputs = [ + { + "type": "uint256[]", + "name": "arg1", + "internalType": "uint256[]" + } + ] + + document.getElementById('first').value = ' 156 000 , 10 690 000 , 59874 ' + const expectedValue = [['156000', '10690000', '59874']] + const $functionInputs = $('[data-function-form]').find('input[name=function_input]') + expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue) +}) \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/_mixins.scss b/apps/block_scout_web/assets/css/_mixins.scss index 911b1b658718..ab5b87fa1176 100644 --- a/apps/block_scout_web/assets/css/_mixins.scss +++ b/apps/block_scout_web/assets/css/_mixins.scss @@ -135,7 +135,7 @@ color: $text-color; cursor: pointer; display: flex; - font-size: 12px; + font-size: $font-size; font-weight: 600; height: 36px; justify-content: center; diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index eccf6f678197..729bee00afd8 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -101,6 +101,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/form"; @import "components/btn_copy"; @import "components/btn_qr"; +@import "components/btn_wallet"; @import "components/btn_contract"; @import "components/btn_address_card"; @import "components/btn_dropdown_line"; diff --git a/apps/block_scout_web/assets/css/components/_address-overview.scss b/apps/block_scout_web/assets/css/components/_address-overview.scss index f758e4affcde..6462722f2fa1 100644 --- a/apps/block_scout_web/assets/css/components/_address-overview.scss +++ b/apps/block_scout_web/assets/css/components/_address-overview.scss @@ -1,9 +1,8 @@ .address-detail-hash-title { color: #333; - font-size: 12px; + font-size: 14px; font-weight: bold; line-height: 1.2; - margin: 0 0 12px; text-align: left; } @@ -66,8 +65,6 @@ } .address-current-balance { - font-size: 12px; - font-weight: 200; line-height: 1.2; margin: 0 0 12px; font-weight: 400; diff --git a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss index d19b662486f0..bf4851c82bcf 100644 --- a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss +++ b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss @@ -4,7 +4,7 @@ $btn-dropdown-line-color-hover: #f5f6fa !default; $btn-dropdown-line-font: #333; .btn-dropdown-line { - @include btn-line($btn-dropdown-line-bg, $btn-dropdown-line-color); + @include btn-line($btn-dropdown-line-bg, $btn-dropdown-line-color, 14px); border-color: $btn-dropdown-line-color; color: $btn-dropdown-line-font; outline: none !important; diff --git a/apps/block_scout_web/assets/css/components/_btn_no_border.scss b/apps/block_scout_web/assets/css/components/_btn_no_border.scss index a958eb72149f..a74bb4530abe 100644 --- a/apps/block_scout_web/assets/css/components/_btn_no_border.scss +++ b/apps/block_scout_web/assets/css/components/_btn_no_border.scss @@ -11,9 +11,9 @@ $btn-no-border-color: $primary !default; } } -.btn-no-border-transactions { +.btn-no-border-link-to-tems { background-color: rgba($primary, 0.1) !important; - border-color: $btn-no-border-bg; + border-color: #00000000 !important; align-items: center; border-radius: 2px; display: flex; diff --git a/apps/block_scout_web/assets/css/components/_btn_wallet.scss b/apps/block_scout_web/assets/css/components/_btn_wallet.scss new file mode 100644 index 000000000000..e0d7c434a5d7 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_btn_wallet.scss @@ -0,0 +1,10 @@ +$btn-wallet-color: $btn-line-color !default; +$btn-wallet-dimensions: 31px !default; + +.btn-wallet-icon { + @include square-icon-button($btn-wallet-color, $btn-wallet-dimensions); + color: $btn-wallet-color; + &:hover { + color: #fff; + } +} diff --git a/apps/block_scout_web/assets/css/components/_button.scss b/apps/block_scout_web/assets/css/components/_button.scss index d743e87b204a..b370f74f919c 100644 --- a/apps/block_scout_web/assets/css/components/_button.scss +++ b/apps/block_scout_web/assets/css/components/_button.scss @@ -95,3 +95,10 @@ $button-secondary-color: $secondary !default; .button-block + .button-block { margin-top: 5px; } + +.spinner { + background-image: url('/images/spinner.svg'); + background-position: right; + background-repeat: no-repeat; + background-size: 30px; +} diff --git a/apps/block_scout_web/assets/css/components/_dropdown.scss b/apps/block_scout_web/assets/css/components/_dropdown.scss index 3fe3831dccd2..3a789d9edb09 100644 --- a/apps/block_scout_web/assets/css/components/_dropdown.scss +++ b/apps/block_scout_web/assets/css/components/_dropdown.scss @@ -101,4 +101,12 @@ $dropdown-menu-item-hover-background: rgba($secondary, 0.1) !default; .dropdown-toggle::after { margin-left: 0.71em; + font-size: 12px !important; +} + +.token-balance-dropdown { + transform: none !important; + top: 20px !important; + left: unset !important; + width: 300px !important; } diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index 965efe034740..caa5a060bf54 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -597,11 +597,6 @@ $cube-quantity: 5; @include media-breakpoint-down(sm) { display: inline-block; word-break: break-all; - } -} - -.bridged-tokens-buttons-mobile { - @include media-breakpoint-down(sm) { - margin-top: 0.5em; + line-height: 20px; } } diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss index 4e680d653d3d..2d61de0e4d05 100644 --- a/apps/block_scout_web/assets/css/components/_transaction.scss +++ b/apps/block_scout_web/assets/css/components/_transaction.scss @@ -8,16 +8,6 @@ } } -.transaction-bottom-panel { - display: flex; - flex-direction: column; - @media (min-width: 768px) { - flex-direction: row; - justify-content: space-between; - align-items: flex-end; - } -} - .download-all-transactions { text-align: center; color: #a3a9b5; @@ -70,7 +60,7 @@ } } -.transaction-details { +.fs-14 { font-size: 14px; } .transaction-gas-used { diff --git a/apps/block_scout_web/assets/css/components/datepicker.scss b/apps/block_scout_web/assets/css/components/datepicker.scss new file mode 100644 index 000000000000..2a8a559b045b --- /dev/null +++ b/apps/block_scout_web/assets/css/components/datepicker.scss @@ -0,0 +1 @@ +@import '~pikaday/scss/pikaday'; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/export-csv.scss b/apps/block_scout_web/assets/css/export-csv.scss new file mode 100644 index 000000000000..045739115a77 --- /dev/null +++ b/apps/block_scout_web/assets/css/export-csv.scss @@ -0,0 +1,12 @@ +@import "components/datepicker"; + +.js-datepicker { + width: 150px !important; + display: inline-block !important; + margin-left: 10px; + margin-right: 10px; +} + +.dates-container { + margin-bottom: 20px; +} \ 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 index 42c46b014a60..109e7db86660 100644 --- a/apps/block_scout_web/assets/css/theme/_dark-theme.scss +++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss @@ -1158,5 +1158,14 @@ $dark-stakes-banned-background: #3e314c; } } .dark-theme-applied .confirmations-label:after { - background: .7rem solid $dark-light-bg; + border-left: .9rem solid $dark-light-bg; +} + +.dark-theme-applied .i-tooltip-2:hover{ + color: $labels-dark; +} + +.dark-theme-applied .btn-no-border-link-to-tems { + border-color: $dark-primary !important; + color: $dark-primary !important; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/lib/ad.js b/apps/block_scout_web/assets/js/lib/ad.js index a0ac1a8ec41a..431808552fb1 100644 --- a/apps/block_scout_web/assets/js/lib/ad.js +++ b/apps/block_scout_web/assets/js/lib/ad.js @@ -9,13 +9,15 @@ function countImpressions (impressionUrl) { function showAd () { const domainName = window.location.hostname - if (domainName === 'blockscout.com') { + if (domainName.endsWith('blockscout.com')) { $('.js-ad-dependant-mb-2').addClass('mb-2') $('.js-ad-dependant-mb-3').addClass('mb-3') + $('.js-ad-dependant-mb-5-reverse').removeClass('mb-5') return true } else { $('.js-ad-dependant-mb-2').removeClass('mb-2') $('.js-ad-dependant-mb-3').removeClass('mb-3') + $('.js-ad-dependant-mb-5-reverse').addClass('mb-5') return false } } diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js index df2ffbf4d6a9..281f0d560d37 100644 --- a/apps/block_scout_web/assets/js/lib/currency.js +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -3,7 +3,12 @@ import numeral from 'numeral' import { BigNumber } from 'bignumber.js' export function formatUsdValue (value) { - return `${formatCurrencyValue(value)} USD` + const formattedValue = formatCurrencyValue(value) + if (formattedValue === 'N/A') { + return formattedValue + } else { + return `${formattedValue} USD` + } } export function formatCGValue (value) { @@ -16,8 +21,8 @@ function formatTokenUsdValue (value) { function formatCurrencyValue (value, symbol) { symbol = symbol || '$' - if (isNaN(value) || value === '0') return 'N/A' - if (value === 0) return `${symbol}0.000000` + if (isNaN(value)) return 'N/A' + if (value === 0 || value === '0') return `${symbol}0.00` if (value < 0.000001) return `${window.localized['Less than']} ${symbol}0.000001` if (value < 10) return `${symbol}${numeral(value).format('0.000')}` if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}` @@ -56,7 +61,10 @@ function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExcha const ether = weiToEther(el.dataset.weiValue) const usd = etherToUSD(ether, usdExchangeRate) const formattedUsd = formatUsdValue(usd) - if (formattedUsd !== el.innerHTML) el.innerHTML = formattedUsd + if (formattedUsd !== el.innerHTML) { + $(el).data('rawUsdValue', usd) + el.innerHTML = formattedUsd + } } function tryUpdateUnitPriceValues (el, usdUnitPrice = el.dataset.usdUnitPrice) { diff --git a/apps/block_scout_web/assets/js/lib/datepicker.js b/apps/block_scout_web/assets/js/lib/datepicker.js new file mode 100644 index 000000000000..4816cdb181b3 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/datepicker.js @@ -0,0 +1,86 @@ +import * as Pikaday from 'pikaday' +import moment from 'moment' +import $ from 'jquery' + +const DATE_FORMAT = 'YYYY-MM-DD' + +const $button = $('#export-csv-button') + +// eslint-disable-next-line +const _instance1 = new Pikaday({ + field: $('.js-datepicker-from')[0], + onSelect: (date) => onSelect(date, 'from_period'), + defaultDate: moment().add(-1, 'months').toDate(), + setDefaultDate: true, + maxDate: new Date(), + format: DATE_FORMAT +}) + +// eslint-disable-next-line +const _instance2 = new Pikaday({ + field: $('.js-datepicker-to')[0], + onSelect: (date) => onSelect(date, 'to_period'), + defaultDate: new Date(), + setDefaultDate: true, + maxDate: new Date(), + format: DATE_FORMAT +}) + +$button.on('click', () => { + $button.addClass('spinner') + // eslint-disable-next-line + const resp = grecaptcha.getResponse() + if (resp) { + $.ajax({ + url: './captcha?type=JSON', + type: 'POST', + headers: { + 'x-csrf-token': $('[name=_csrf_token]').val() + }, + data: { + type: 'JSON', + captchaResponse: resp + } + }) + .done(function (data) { + // eslint-disable-next-line + grecaptcha.reset() + const dataJson = JSON.parse(data) + if (dataJson.success) { + $button.removeClass('spinner') + location.href = $button.data('link') + } else { + $button.removeClass('spinner') + return false + } + }) + .fail(function (_jqXHR, textStatus) { + $button.removeClass('spinner') + }) + } else { + $button.removeClass('spinner') + } +}) + +function onSelect (date, paramToReplace) { + const formattedDate = moment(date).format(DATE_FORMAT) + + if (date) { + var csvExportPath = $button.data('link') + + var updatedCsvExportUrl = replaceUrlParam(csvExportPath, paramToReplace, formattedDate) + $button.data('link', updatedCsvExportUrl) + } +} + +function replaceUrlParam (url, paramName, paramValue) { + if (paramValue == null) { + paramValue = '' + } + var pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)') + if (url.search(pattern) >= 0) { + return url.replace(pattern, '$1' + paramValue + '$2') + } + url = url.replace(/[?#]$/, '') + return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue +} diff --git a/apps/block_scout_web/assets/js/lib/from_now.js b/apps/block_scout_web/assets/js/lib/from_now.js index 1c95f350750e..32dc90b0fc1e 100644 --- a/apps/block_scout_web/assets/js/lib/from_now.js +++ b/apps/block_scout_web/assets/js/lib/from_now.js @@ -22,7 +22,7 @@ function updateAge (el, timestamp) { let fromNow = timestamp.fromNow() // show the exact time only for transaction details page. Otherwise, short entry const elInTile = el.hasAttribute('in-tile') - if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/blocks/')) && !elInTile) { + if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/block/') || window.location.pathname.includes('/blocks/')) && !elInTile) { const offset = moment().utcOffset() / 60 const sign = offset && Math.sign(offset) ? '+' : '-' const formatDate = `MMMM-DD-YYYY hh:mm:ss A ${sign}${offset} UTC` diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js index 5a03a3afa4ac..4ed34840be4d 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js @@ -23,8 +23,8 @@ export function prepareMethodArgs ($functionInputs, inputs) { const inputType = inputs[ind] && inputs[ind].type const inputComponents = inputs[ind] && inputs[ind].components let sanitizedInputValue - sanitizedInputValue = replaceSpaces(inputValue, inputType, inputComponents) - sanitizedInputValue = replaceDoubleQuotes(sanitizedInputValue, inputType, inputComponents) + sanitizedInputValue = replaceDoubleQuotes(inputValue, inputType, inputComponents) + sanitizedInputValue = replaceSpaces(sanitizedInputValue, inputType, inputComponents) if (isArrayInputType(inputType) || isTupleInputType(inputType)) { if (sanitizedInputValue === '' || sanitizedInputValue === '[]') { @@ -38,6 +38,7 @@ export function prepareMethodArgs ($functionInputs, inputs) { const elementInputType = inputType.split('[')[0] var sanitizedElementValue = replaceDoubleQuotes(elementValue, elementInputType) + sanitizedElementValue = replaceSpaces(sanitizedElementValue, elementInputType) if (isBoolInputType(elementInputType)) { sanitizedElementValue = convertToBool(elementValue) @@ -69,6 +70,18 @@ export const formatError = (error) => { return message } +export const formatTitleAndError = (error) => { + let { message } = error + var title = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message + title = title && title.split('{').length > 1 ? title.split('{')[0].replace(':', '') : title + try { + message = message && message.indexOf('{') >= 0 ? JSON.parse(message && message.slice(message.indexOf('{'))).error : '' + } catch (exception) { + message = '' + } + return { title: title, message: message } +} + export const getCurrentAccount = () => { return new Promise((resolve, reject) => { window.ethereum.request({ method: 'eth_accounts' }) @@ -129,7 +142,7 @@ function replaceSpaces (value, type, components) { }) .join(',') } else { - return value + return value.trim() } } diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js index 55312c1429d2..e6e444c2b02b 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js @@ -1,6 +1,6 @@ import $ from 'jquery' import { getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers' -import { walletEnabled, connectToWallet, shouldHideConnectButton, callMethod } from './write' +import { walletEnabled, connectToWallet, shouldHideConnectButton, callMethod, queryMethod } from './write' import '../../pages/address' const loadFunctions = (element) => { @@ -120,14 +120,10 @@ const readWriteFunction = (element) => { const inputs = getMethodInputs(contractAbi, functionName) const $methodId = $form.find('input[name=method_id]') const args = prepareMethodArgs($functionInputs, inputs) + const type = $('[data-smart-contract-functions]').data('type') - const data = { - function_name: functionName, - method_id: $methodId.val(), - args - } - - $.get(url, data, response => $responseContainer.html(response)) + walletEnabled() + .then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer)) } else if (action === 'write') { const explorerChainId = $form.data('chainId') walletEnabled() diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/write.js b/apps/block_scout_web/assets/js/lib/smart_contract/write.js index e7bb4716018f..a9be3c5bce09 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/write.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/write.js @@ -1,6 +1,7 @@ import Web3 from 'web3' +import $ from 'jquery' import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' -import { compareChainIDs, formatError, getContractABI, getCurrentAccount, getMethodInputs, prepareMethodArgs } from './common_helpers' +import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccount, getMethodInputs, prepareMethodArgs } from './common_helpers' export const walletEnabled = () => { return new Promise((resolve) => { @@ -100,7 +101,8 @@ export function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $ const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) methodToCall .on('error', function (error) { - openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) + var titleAndError = formatTitleAndError(error) + openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, titleAndError.message, false) }) .on('transactionHash', function (txHash) { onTransactionHash(txHash, $element, functionName) @@ -128,6 +130,31 @@ export function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $ }) } +export function queryMethod (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) { + var data = { + function_name: functionName, + method_id: $methodId.val(), + type: type, + args + } + if (isWalletEnabled) { + getCurrentAccount() + .then((currentAccount) => { + data = { + function_name: functionName, + method_id: $methodId.val(), + type: type, + from: currentAccount, + args + } + $.get(url, data, response => $responseContainer.html(response)) + } + ) + } else { + $.get(url, data, response => $responseContainer.html(response)) + } +} + function onTransactionHash (txHash, $element, functionName) { openModalWithMessage($element.find('#pending-contract-write'), true, txHash) const getTxReceipt = (txHash) => { diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js index 5f07156d6d46..1a64f549db2d 100644 --- a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import { formatAllUsdValues } from './currency' +import { formatAllUsdValues, formatUsdValue } from './currency' import { TokenBalanceDropdownSearch } from './token_balance_dropdown_search' const tokenBalanceDropdown = (element) => { @@ -12,6 +12,15 @@ const tokenBalanceDropdown = (element) => { .done(response => { const responseHtml = formatAllUsdValues($(response)) $element.html(responseHtml) + const tokensCount = $('[data-dropdown-token-balance-test]').length + const $addressTokenWorth = $('[data-test="address-tokens-worth"]') + const tokensDsName = (tokensCount > 1) ? ' tokens' : ' token' + $('[data-test="address-tokens-panel-tokens-worth"]').text(`${$addressTokenWorth.text()} | ${tokensCount} ${tokensDsName}`) + const $addressTokensPanelNativeWorth = $('[data-test="address-tokens-panel-native-worth"]') + const rawUsdValue = $addressTokensPanelNativeWorth.children('span').data('raw-usd-value') + const rawUsdTokensValue = $addressTokenWorth.data('usd-value') + const formattedFullUsdValue = formatUsdValue(parseFloat(rawUsdValue) + parseFloat(rawUsdTokensValue)) + $('[data-test="address-tokens-panel-net-worth"]').text(formattedFullUsdValue) }) .fail(() => { $loading.hide() diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 577b6a15f471..f60fefd631d5 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -24,6 +24,7 @@ export const initialState = { balanceCard: null, fetchedCoinBalanceBlockNumber: null, transactionCount: null, + tokenTransferCount: null, gasUsageCount: null, validationCount: null, countersFetched: false @@ -45,6 +46,7 @@ export function reducer (state = initialState, action) { case 'COUNTERS_FETCHED': { return Object.assign({}, state, { transactionCount: action.transactionCount, + tokenTransferCount: action.tokenTransferCount, gasUsageCount: action.gasUsageCount, validationCount: action.validationCount, countersFetched: true @@ -63,6 +65,13 @@ export function reducer (state = initialState, action) { return Object.assign({}, state, { transactionCount }) } + case 'RECEIVED_NEW_TOKEN_TRANSFER': { + if (state.channelDisconnected) return state + + const tokenTransferCount = (action.msg.fromAddressHash === state.addressHash) ? state.tokenTransferCount + 1 : state.tokenTransferCount + + return Object.assign({}, state, { tokenTransferCount }) + } case 'RECEIVED_UPDATED_BALANCE': { return Object.assign({}, state, { balanceCard: action.msg.balanceCard, @@ -109,12 +118,30 @@ const elements = { render ($el, state, oldState) { if (state.countersFetched && state.transactionCount) { if (oldState.transactionCount === state.transactionCount) return - $el.empty().append(numeral(state.transactionCount).format() + ' Transactions') + const transactionsDSName = (state.transactionCount > 1) ? ' Transactions' : ' Transaction' + $el.empty().append(numeral(state.transactionCount).format() + transactionsDSName) $el.show() - $el.parent('.address-detail-item').removeAttr('style') + $('.address-transactions-count-item').removeAttr('style') } else { $el.hide() - $el.parent('.address-detail-item').css('display', 'none') + $('.address-transactions-count-item').css('display', 'none') + } + } + }, + '[data-selector="transfer-count"]': { + load ($el) { + return { tokenTransferCount: numeral($el.text()).value() } + }, + render ($el, state, oldState) { + if (state.countersFetched && state.tokenTransferCount) { + if (oldState.tokenTransferCount === state.tokenTransferCount) return + const transfersDSName = (state.tokenTransferCount > 1) ? ' Transfers' : ' Transfer' + $el.empty().append(numeral(state.tokenTransferCount).format() + transfersDSName) + $el.show() + $('.address-transfers-count-item').removeAttr('style') + } else { + $el.hide() + $('.address-transfers-count-item').css('display', 'none') } } }, @@ -125,12 +152,12 @@ const elements = { render ($el, state, oldState) { if (state.countersFetched && state.gasUsageCount) { if (oldState.gasUsageCount === state.gasUsageCount) return - $el.empty().append(numeral(state.gasUsageCount).format() + ' Gas used') + $el.empty().append(numeral(state.gasUsageCount).format()) $el.show() - $el.parent('.address-detail-item').removeAttr('style') + $('.address-gas-used-item').removeAttr('style') } else { $el.hide() - $el.parent('.address-detail-item').css('display', 'none') + $('.address-gas-used-item').css('display', 'none') } } }, @@ -150,10 +177,10 @@ const elements = { render ($el, state, oldState) { if (state.countersFetched && state.validationCount) { if (oldState.validationCount === state.validationCount) return - $el.empty().append(numeral(state.validationCount).format() + ' Blocks Validated') - $el.show() + $el.empty().append(numeral(state.validationCount).format()) + $('.address-validation-count-item').removeAttr('style') } else { - $el.hide() + $('.address-validation-count-item').css('display', 'none') } } } @@ -202,6 +229,12 @@ if ($addressDetailsPage.length) { msg: humps.camelizeKeys(msg) }) }) + addressChannel.on('transfer', (msg) => { + store.dispatch({ + type: 'RECEIVED_NEW_TOKEN_TRANSFER', + msg: humps.camelizeKeys(msg) + }) + }) const blocksChannel = socket.channel(`blocks:${addressHash}`, {}) blocksChannel.join() diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index c848c4934bf0..3d38e60daf8b 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -123,3 +123,34 @@ if ($transactionDetailsPage.length) { }) }) } + +$(function () { + const $collapseButton = $('[button-collapse-input]') + const $expandButton = $('[button-expand-input]') + + $collapseButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $collapseButton.classList.add('d-none') + $expandButton.classList.remove('d-none') + $hiddenText.classList.add('d-none') + $placeHolder.classList.remove('d-none') + }) + + $expandButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $expandButton.classList.add('d-none') + $collapseButton.classList.remove('d-none') + $hiddenText.classList.remove('d-none') + $placeHolder.classList.add('d-none') + }) +}) diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js index 752ef9e07373..dc4135e05fcc 100644 --- a/apps/block_scout_web/assets/js/pages/verification_form.js +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -233,6 +233,7 @@ if ($contractVerificationPage.length) { if ($(this).prop('checked')) { $('#verify_via_flattened_code_button').show() $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').hide() } }) @@ -240,6 +241,15 @@ if ($contractVerificationPage.length) { if ($(this).prop('checked')) { $('#verify_via_flattened_code_button').hide() $('#verify_via_sourcify_button').show() + $('#verify_vyper_contract_button').hide() + } + }) + + $('.verify-vyper-contract').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').hide() + $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').show() } }) } diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 801f20ecb76b..e62188d33644 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -1269,9 +1269,9 @@ } }, "@ethersproject/bignumber": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.1.tgz", - "integrity": "sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.2.tgz", + "integrity": "sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==", "requires": { "@ethersproject/bytes": "^5.4.0", "@ethersproject/logger": "^5.4.0", @@ -1616,15 +1616,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -2149,6 +2140,12 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "@types/node": { "version": "13.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", @@ -2453,14 +2450,6 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } } }, "ajv": { @@ -2660,6 +2649,12 @@ "es-abstract": "^1.17.0-next.1" } }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -3428,21 +3423,14 @@ "dev": true }, "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - } + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" } }, "caniuse-api": { @@ -3934,15 +3922,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -4134,9 +4113,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -4376,15 +4355,6 @@ } } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -4428,6 +4398,24 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -6182,6 +6170,12 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6477,9 +6471,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -6557,13 +6551,10 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, "infer-owner": { "version": "1.0.4", @@ -6842,6 +6833,15 @@ } } }, + "is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -6905,12 +6905,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7160,12 +7154,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8919,16 +8907,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -8989,9 +8967,9 @@ "dev": true }, "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, "map-visit": { @@ -9035,101 +9013,190 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" }, "dependencies": { "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "lru-cache": "^6.0.0" } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "p-locate": "^4.1.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" } }, - "pify": { + "p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "lru-cache": "^6.0.0" } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, @@ -9211,6 +9278,12 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "mini-css-extract-plugin": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", @@ -9270,6 +9343,17 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, "minipass": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", @@ -9529,15 +9613,6 @@ "which": "^2.0.2" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -9602,9 +9677,9 @@ "dev": true }, "node-sass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-5.0.0.tgz", - "integrity": "sha512-opNgmlu83ZCF792U281Ry7tak9IbVC+AKnXGovcQ8LG8wFaJv6cLnRlc6DIHlmNxWEexB5bZxi9SZ9JyUuOYjw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-6.0.1.tgz", + "integrity": "sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -9614,8 +9689,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "lodash": "^4.17.15", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", + "meow": "^9.0.0", "nan": "^2.13.2", "node-gyp": "^7.1.0", "npmlog": "^4.0.0", @@ -10095,9 +10169,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-parser": { @@ -10153,20 +10227,10 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } + "pikaday": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz", + "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" }, "pirates": { "version": "4.0.1", @@ -10499,9 +10563,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10539,9 +10603,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10583,9 +10647,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10621,9 +10685,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -11036,6 +11100,12 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11144,13 +11214,13 @@ } }, "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, "reduce-reducers": { @@ -11271,15 +11341,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -11442,6 +11503,15 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -11777,9 +11847,9 @@ } }, "sass-loader": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-11.1.1.tgz", - "integrity": "sha512-fOCp/zLmj1V1WHDZbUbPgrZhA7HKXHEqkslzB+05U5K9SbSbcmH91C7QLW31AsXikxUMaxXRhhcqWZAxUMLDyA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", + "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", "dev": true, "requires": { "klona": "^2.0.4", @@ -12567,12 +12637,12 @@ } }, "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "get-stdin": "^4.0.1" + "min-indent": "^1.0.0" } }, "strip-json-comments": { @@ -12787,6 +12857,11 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, "tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -12873,9 +12948,9 @@ "dev": true }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -13160,9 +13235,9 @@ } }, "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, "true-case-path": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 5f03dee95d44..cfe6b4598d42 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -48,6 +48,7 @@ "path-parser": "^4.2.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", + "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^0.4.3", "uniqid": "^5.4.0", @@ -79,10 +80,10 @@ "file-loader": "^6.2.0", "jest": "^25.1.0", "mini-css-extract-plugin": "^1.6.2", - "node-sass": "^5.0.0", + "node-sass": "^6.0.1", "postcss": "^8.3.5", "postcss-loader": "^4.3.0", - "sass-loader": "^11.1.1", + "sass-loader": "12.1", "style-loader": "^1.3.0", "webpack": "^5.44.0", "webpack-cli": "^4.7.2" diff --git a/apps/block_scout_web/assets/static/images/spinner.svg b/apps/block_scout_web/assets/static/images/spinner.svg new file mode 100644 index 000000000000..dd29cb703c25 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/spinner.svg @@ -0,0 +1,52 @@ + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index a14c78a169c1..e7d43be8622f 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -98,7 +98,9 @@ const appJs = 'banner': './js/lib/banner.js', 'autocomplete': './js/lib/autocomplete.js', 'search-results': './js/pages/search-results/search.js', - 'token-overview': './js/pages/token/overview.js' + 'token-overview': './js/pages/token/overview.js', + 'export-csv': './css/export-csv.scss', + 'datepicker': './js/lib/datepicker.js' }, output: { filename: '[name].js', diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 8e10f031a957..cfb9b0d17bf3 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -52,7 +52,10 @@ config :block_scout_web, dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"), circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"), test_tokens_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN"), - max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50")) + max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50")), + max_length_to_show_string_without_trimming: System.get_env("MAX_STRING_LENGTH_WITHOUT_TRIMMING", "2040"), + re_captcha_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY", nil), + re_captcha_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY", nil) config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 7adda14b274f..fa370b2895b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -218,8 +218,16 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {value, address_hash}}] end + def paging_options(%{"token_name" => name, "token_type" => type, "value" => value}) do + [paging_options: %{@default_paging_options | key: {name, type, value}}] + end + def paging_options(_params), do: [paging_options: @default_paging_options] + def put_key_value_to_paging_options([paging_options: paging_options], key, value) do + [paging_options: Map.put(paging_options, key, value)] + end + def param_to_block_number(formatted_number) when is_binary(formatted_number) do case Integer.parse(formatted_number) do {number, ""} -> {:ok, number} @@ -323,6 +331,10 @@ defmodule BlockScoutWeb.Chain do %{"address_hash" => to_string(address_hash), "value" => Decimal.to_integer(value)} end + defp paging_params({%CurrentTokenBalance{value: value}, _, %Token{name: name, type: type}}) do + %{"token_name" => name, "token_type" => type, "value" => Decimal.to_integer(value)} + end + defp paging_params(%CoinBalance{block_number: block_number}) do %{"block_number" => block_number} end diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index f84c72df9304..7dcd553c4137 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -200,7 +200,7 @@ defmodule BlockScoutWeb.AddressChannel do rendered = View.render_to_string( AddressView, - "_balance_card.html", + "_balance_dropdown.html", conn: socket, address: address, coin_balance_status: :current, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex index c2021cf26aee..ccb9c12fc2d7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -7,7 +7,9 @@ defmodule BlockScoutWeb.AddressContractVerificationController do alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.SmartContract - alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} + alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker + alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker alias Explorer.ThirdPartyIntegrations.Sourcify def new(conn, %{"address_id" => address_hash_string}) do @@ -26,7 +28,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do ) compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(:solc) do {:ok, compiler_versions} -> compiler_versions @@ -50,7 +52,18 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "external_libraries" => external_libraries } ) do - Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + Que.add(SolidityPublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + + send_resp(conn, 204, "") + end + + def create( + conn, + %{ + "smart_contract" => smart_contract + } + ) do + Que.add(VyperPublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) send_resp(conn, 204, "") end @@ -104,7 +117,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do end def create(conn, _params) do - Que.add(PublisherWorker, {"", %{}, %{}, conn}) + Que.add(SolidityPublisherWorker, {"", %{}, %{}, conn}) send_resp(conn, 204, "") end @@ -155,14 +168,19 @@ defmodule BlockScoutWeb.AddressContractVerificationController do end defp process_metadata_and_publish(address_hash_string, verification_metadata, is_partial, conn \\ nil) do - %{"params_to_publish" => params_to_publish, "abi" => abi, "secondary_sources" => secondary_sources} = - parse_params_from_sourcify(address_hash_string, verification_metadata) + %{ + "params_to_publish" => params_to_publish, + "abi" => abi, + "secondary_sources" => secondary_sources, + "compilation_target_file_path" => compilation_target_file_path + } = parse_params_from_sourcify(address_hash_string, verification_metadata) ContractController.publish(conn, %{ "addressHash" => address_hash_string, "params" => Map.put(params_to_publish, "partially_verified", is_partial), "abi" => abi, - "secondarySources" => secondary_sources + "secondarySources" => secondary_sources, + "compilationTargetFilePath" => compilation_target_file_path }) end @@ -207,7 +225,8 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "params_to_publish" => extract_primary_source_code(content, Map.get(full_params_acc, "params_to_publish")), "abi" => Map.get(full_params_acc, "abi"), "secondary_sources" => Map.get(full_params_acc, "secondary_sources"), - "compilation_target_file_name" => Map.get(full_params_acc, "compilation_target_file_name") + "compilation_target_file_path" => Map.get(full_params_acc, "compilation_target_file_path"), + "compilation_target_file_name" => compilation_target_file_name } else secondary_sources = [ @@ -218,16 +237,26 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "params_to_publish" => Map.get(full_params_acc, "params_to_publish"), "abi" => Map.get(full_params_acc, "abi"), "secondary_sources" => secondary_sources, - "compilation_target_file_name" => Map.get(full_params_acc, "compilation_target_file_name") + "compilation_target_file_path" => Map.get(full_params_acc, "compilation_target_file_path"), + "compilation_target_file_name" => compilation_target_file_name } end end) end - defp prepare_additional_source(address_hash_string, %{"name" => name, "content" => content, "path" => _path}) do + defp prepare_additional_source(address_hash_string, %{"name" => _name, "content" => content, "path" => path}) do + splitted_path = + path + |> String.split("/") + + trimmed_path = + splitted_path + |> Enum.slice(9..Enum.count(splitted_path)) + |> Enum.join("/") + %{ "address_hash" => address_hash_string, - "file_name" => name, + "file_name" => "/" <> trimmed_path, "contract_source_code" => content } end @@ -261,6 +290,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do %{ "params_to_publish" => params, "abi" => abi, + "compilation_target_file_path" => compilation_target_file_path, "compilation_target_file_name" => compilation_target_file_name, "secondary_sources" => [] } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex index 2427d82aedbb..e36ef050f524 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do alias BlockScoutWeb.Controller alias Explorer.Chain alias Explorer.Chain.SmartContract - alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker} def new(conn, %{"address_id" => address_hash_string}) do if Chain.smart_contract_fully_verified?(address_hash_string) do @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do ) compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(:solc) do {:ok, compiler_versions} -> compiler_versions diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex new file mode 100644 index 000000000000..f1cb3832e2e6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex @@ -0,0 +1,47 @@ +defmodule BlockScoutWeb.AddressContractVerificationVyperController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker} + + def new(conn, %{"address_id" => address_hash_string}) do + changeset = + SmartContract.changeset( + %SmartContract{address_hash: address_hash_string}, + %{} + ) + + compiler_versions = + case CompilerVersion.fetch_versions(:vyper) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + + render(conn, "new.html", + changeset: changeset, + compiler_versions: compiler_versions, + address_hash: address_hash_string + ) + end + + def create( + conn, + %{ + "smart_contract" => smart_contract + } + ) do + Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) + + send_resp(conn, 204, "") + end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index 9bfe2b5b0199..c8e90a2b653f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias BlockScoutWeb.{AccessHelpers, AddressView, Controller} - alias Explorer.Counters.{AddressTransactionsCounter, AddressTransactionsGasUsageCounter} + alias Explorer.Counters.{AddressTokenTransfersCounter, AddressTransactionsCounter, AddressTransactionsGasUsageCounter} alias Explorer.{Chain, Market} alias Explorer.ExchangeRates.Token alias Phoenix.View @@ -84,10 +84,11 @@ defmodule BlockScoutWeb.AddressController do def address_counters(conn, %{"id" => address_hash_string}) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address} <- Chain.hash_to_address(address_hash) do - {transaction_count, gas_usage_count, validation_count} = transaction_and_validation_count(address) + {transaction_count, token_transfer_count, gas_usage_count, validation_count} = address_counters(address) json(conn, %{ transaction_count: transaction_count, + token_transfer_count: token_transfer_count, gas_usage_count: gas_usage_count, validation_count: validation_count }) @@ -95,18 +96,24 @@ defmodule BlockScoutWeb.AddressController do _ -> json(conn, %{ transaction_count: 0, + token_transfer_count: 0, gas_usage_count: 0, validation_count: 0 }) end end - def transaction_and_validation_count(address) do + defp address_counters(address) do transaction_count_task = Task.async(fn -> transaction_count(address) end) + token_transfer_count_task = + Task.async(fn -> + token_transfers_count(address) + end) + gas_usage_count_task = Task.async(fn -> gas_usage_count(address) @@ -117,7 +124,7 @@ defmodule BlockScoutWeb.AddressController do validation_count(address) end) - [transaction_count_task, gas_usage_count_task, validation_count_task] + [transaction_count_task, token_transfer_count_task, gas_usage_count_task, validation_count_task] |> Task.yield_many(:timer.seconds(60)) |> Enum.map(fn {_task, res} -> case res do @@ -138,6 +145,10 @@ defmodule BlockScoutWeb.AddressController do AddressTransactionsCounter.fetch(address) end + def token_transfers_count(address) do + AddressTokenTransfersCounter.fetch(address) + end + def gas_usage_count(address) do AddressTransactionsGasUsageCounter.fetch(address) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex index 11e148a39081..fd59482ad1c8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex @@ -26,12 +26,12 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do token_balances_except_bridged = token_balances - |> Enum.filter(fn {token_balance, _} -> !token_balance.token.bridged end) + |> Enum.filter(fn {token_balance, _, _} -> !token_balance.token.bridged end) circles_total_balance = if Enum.count(circles_addresses_list) > 0 do token_balances_except_bridged - |> Enum.reduce(Decimal.new(0), fn {token_balance, _}, acc_balance -> + |> Enum.reduce(Decimal.new(0), fn {token_balance, _, _}, acc_balance -> {:ok, token_address} = Chain.hash_to_address(token_balance.address_hash) from_address = from_address_hash(token_address) @@ -60,7 +60,8 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do |> render("_token_balances.html", address_hash: Address.checksum(address_hash), token_balances: token_balances_with_price, - circles_total_balance: circles_total_balance + circles_total_balance: circles_total_balance, + conn: conn ) _ -> @@ -70,7 +71,8 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do |> render("_token_balances.html", address_hash: Address.checksum(address_hash), token_balances: [], - circles_total_balance: Decimal.new(0) + circles_total_balance: Decimal.new(0), + conn: conn ) end else diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index a9f017252a86..96ab130d56f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -14,8 +14,12 @@ defmodule BlockScoutWeb.AddressTokenController do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, address} <- Chain.hash_to_address(address_hash, [], false), {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do - tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params)) - {tokens, next_page} = split_list_by_page(tokens_plus_one) + token_balances_plus_one = + address_hash + |> Chain.fetch_last_token_balances(paging_options(params)) + |> Market.add_price() + + {tokens, next_page} = split_list_by_page(token_balances_plus_one) next_page_path = case next_page_params(next_page, tokens, params) do @@ -29,11 +33,13 @@ defmodule BlockScoutWeb.AddressTokenController do items = tokens |> Market.add_price() - |> Enum.map(fn token -> + |> Enum.map(fn {token_balance, bridged_token, token} -> View.render_to_string( AddressTokenView, "_tokens.html", + token_balance: token_balance, token: token, + bridged_token: bridged_token, address: address, conn: conn ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index a5cfdafd5a3c..f0dd1e12184f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -9,6 +9,14 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView} alias Explorer.{Chain, Market} + + alias Explorer.Chain.{ + AddressInternalTransactionCsvExporter, + AddressLogCsvExporter, + AddressTokenTransferCsvExporter, + AddressTransactionCsvExporter + } + alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -139,4 +147,105 @@ defmodule BlockScoutWeb.AddressTransactionController do end end end + + def token_transfers_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) + when is_binary(address_hash_string) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTokenTransferCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=token_transfers.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def token_transfers_csv(conn, _), do: not_found(conn) + + def transactions_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTransactionCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def transactions_csv(conn, _), do: not_found(conn) + + def internal_transactions_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressInternalTransactionCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=internal_transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def internal_transactions_csv(conn, _), do: not_found(conn) + + def logs_csv(conn, %{"address_id" => address_hash_string, "from_period" => from_period, "to_period" => to_period}) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressLogCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=logs.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def logs_csv(conn, _), do: not_found(conn) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 5d3662ce759c..d1d6f9f7694c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -8,7 +8,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.{Hash, SmartContract} - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher + alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher alias Explorer.ThirdPartyIntegrations.Sourcify def verify(conn, %{"addressHash" => address_hash} = params) do @@ -198,24 +199,58 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end - def publish_without_broadcast(%{"addressHash" => address_hash, "params" => params, "abi" => abi} = input) do - params = - if Map.has_key?(input, "secondarySources") do - params - |> Map.put("secondary_sources", Map.get(input, "secondarySources")) - else - params - end + def verify_vyper_contract(conn, %{"addressHash" => address_hash} = params) do + with {:params, {:ok, fetched_params}} <- {:params, fetch_vyper_verify_params(params)}, + {:format, {:ok, casted_address_hash}} <- to_address_hash(address_hash), + {:publish, {:ok, _}} <- + {:publish, VyperPublisher.publish(address_hash, fetched_params)} do + address = Chain.address_hash_to_address_with_source_code(casted_address_hash) - case Publisher.publish_smart_contract(address_hash, params, abi) do - {:ok, _contract} = result -> - result + render(conn, :verify, %{contract: address}) + else + {:publish, + {:error, + %Ecto.Changeset{ + errors: [ + address_hash: + {"has already been taken", + [ + constraint: :unique, + constraint_name: "smart_contracts_address_hash_index" + ]} + ] + }}} -> + render(conn, :error, error: "Smart-contract already verified.") - {:error, changeset} -> - {:error, changeset} + {:publish, _} -> + render(conn, :error, error: "Something went wrong while publishing the contract.") + + {:format, :error} -> + render(conn, :error, error: "Invalid address hash") + + {:params, {:error, error}} -> + render(conn, :error, error: error) end end + def publish_without_broadcast( + %{"addressHash" => address_hash, "abi" => abi, "compilationTargetFilePath" => file_path} = input + ) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi, file_path) + |> proccess_response() + end + + def publish_without_broadcast(%{"addressHash" => address_hash, "abi" => abi} = input) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi) + |> proccess_response() + end + def publish(nil, %{"addressHash" => _address_hash} = input) do publish_without_broadcast(input) end @@ -226,6 +261,25 @@ defmodule BlockScoutWeb.API.RPC.ContractController do EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) end + def proccess_params(input) do + if Map.has_key?(input, "secondarySources") do + input["params"] + |> Map.put("secondary_sources", Map.get(input, "secondarySources")) + else + input["params"] + end + end + + def proccess_response(response) do + case response do + {:ok, _contract} = result -> + result + + {:error, changeset} -> + {:error, changeset} + end + end + def listcontracts(conn, params) do with pagination_options <- Helpers.put_pagination_options(%{}, params), {:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do @@ -422,6 +476,15 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> parse_optimization_runs() end + defp fetch_vyper_verify_params(params) do + {:ok, %{}} + |> required_param(params, "addressHash", "address_hash") + |> required_param(params, "name", "name") + |> required_param(params, "compilerVersion", "compiler_version") + |> required_param(params, "contractSourceCode", "contract_source_code") + |> optional_param(params, "constructorArguments", "constructor_arguments") + end + defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do {:ok, Map.put(opts, "optimization_runs", 200)} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex index 07b17141413a..59431742ad87 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do alias Explorer.Chain alias Explorer.Chain.Hash.Address - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher def create(conn, params) do with {:ok, hash} <- validate_address_hash(params["address_hash"]), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex index 445b70b5c2cb..6c09c9361de0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.BlockController do |> Map.delete("type") |> Map.put("block_type", block_type) - block_path( + blocks_path( conn, :index, params_with_block_type diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex index db99f5ae6453..8de421098f9b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.BlockTransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.Chain, - only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + only: [paging_options: 1, put_key_value_to_paging_options: 3, next_page_params: 3, split_list_by_page: 1] import Explorer.Chain, only: [hash_to_block: 2, number_to_block: 2, string_to_block_hash: 1] @@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do [to_address: :names] => :optional } ], - paging_options(params) + put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true) ) transactions_plus_one = Chain.block_to_transactions(block.hash, full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex new file mode 100644 index 000000000000..ad170262a6e9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex @@ -0,0 +1,16 @@ +defmodule BlockScoutWeb.CaptchaController do + use BlockScoutWeb, :controller + + alias Plug.Conn + + def index(conn, %{"captchaResponse" => captcha_response, "type" => "JSON"}) do + body = "secret=#{Application.get_env(:block_scout_web, :re_captcha_secret_key)}&response=#{captcha_response}" + + headers = [{"Content-type", "application/x-www-form-urlencoded"}] + + case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> + Conn.resp(conn, status_code, body) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex new file mode 100644 index 000000000000..8cab1d24058b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.CsvExportController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias Explorer.Chain + + def index(conn, %{"address" => address_hash_string, "type" => type} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + true <- supported_export_type(type) do + render(conn, "index.html", address_hash_string: address_hash_string, type: type) + else + _ -> + not_found(conn) + end + end + + def index(conn, _params) do + not_found(conn) + end + + defp supported_export_type(type) do + Enum.member?(supported_types(), type) + end + + defp supported_types do + [ + "internal-transactions", + "transactions", + "token-transfers", + "logs" + ] + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index d6b198fbdda9..7ea40921089c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -42,6 +42,17 @@ defmodule BlockScoutWeb.SmartContractController do end end + read_functions_required_wallet = + if action == "read" do + if contract_type == "proxy" do + Reader.read_functions_required_wallet_proxy(implementation_address_hash_string) + else + Reader.read_functions_required_wallet(address_hash) + end + else + [] + end + contract_abi = Poison.encode!(address.smart_contract.abi) implementation_abi = @@ -58,6 +69,7 @@ defmodule BlockScoutWeb.SmartContractController do |> put_layout(false) |> render( "_functions.html", + read_functions_required_wallet: read_functions_required_wallet, read_only_functions: functions, address: address, contract_abi: contract_abi, @@ -93,16 +105,26 @@ defmodule BlockScoutWeb.SmartContractController do with true <- ajax?(conn), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), - {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do - contract_type = if Chain.proxy_contract?(address.hash, address.smart_contract.abi), do: :proxy, else: :regular + {:ok, _address} <- Chain.find_contract_address(address_hash, address_options, true) do + contract_type = if params["type"] == "proxy", do: :proxy, else: :regular %{output: outputs, names: names} = - Reader.query_function_with_names( - address_hash, - %{method_id: params["method_id"], args: params["args"]}, - contract_type, - params["function_name"] - ) + if params["from"] do + Reader.query_function_with_names( + address_hash, + %{method_id: params["method_id"], args: params["args"]}, + contract_type, + params["function_name"], + params["from"] + ) + else + Reader.query_function_with_names( + address_hash, + %{method_id: params["method_id"], args: params["args"]}, + contract_type, + params["function_name"] + ) + end conn |> put_status(200) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex index 1c5b9651e26e..c7b12173ae38 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex @@ -35,7 +35,8 @@ defmodule BlockScoutWeb.Tokens.HolderController do View.render_to_string(HolderView, "_token_balances.html", address_hash: address_hash, token_balance: token_balance, - token: token + token: token, + conn: conn ) end) diff --git a/apps/block_scout_web/lib/block_scout_web/csp_header.ex b/apps/block_scout_web/lib/block_scout_web/csp_header.ex index f393323efa4e..824291b42414 100644 --- a/apps/block_scout_web/lib/block_scout_web/csp_header.ex +++ b/apps/block_scout_web/lib/block_scout_web/csp_header.ex @@ -13,12 +13,12 @@ defmodule BlockScoutWeb.CSPHeader do "content-security-policy" => "\ connect-src 'self' #{websocket_endpoints(conn)} https://request-global.czilladx.com/ https://raw.githubusercontent.com/trustwallet/assets/;\ default-src 'self';\ - script-src 'self' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' https://cdn.segment.com https://api.segment.io https://coinzillatag.com;\ + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://coinzillatag.com https://www.google.com https://www.gstatic.com;\ style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ img-src 'self' * data:;\ media-src 'self' * data:;\ font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ - frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/;\ + frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/ https://www.google.com;\ " }) end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index f7aff41f503c..f67c3f09e534 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -514,7 +514,8 @@ defmodule BlockScoutWeb.Etherscan do """, "ContractName" => "Test", "CompilerVersion" => "v0.2.1-2016-01-30-91a6b35", - "OptimizationUsed" => "1" + "OptimizationUsed" => "1", + "FileName" => "{sourcify path or empty}" } } @@ -2562,6 +2563,76 @@ defmodule BlockScoutWeb.Etherscan do ] } + @contract_verify_vyper_contract_action %{ + name: "verify_vyper_contract", + description: """ + Verify a vyper contract with its source code and contract creation information. +curl POST example:
++ <% token_price = if @token_balance.token.usd_value, do: @token_balance.token.usd_value, else: nil %> + <%= ChainView.format_currency_value(token_price, "@") %> +
++ <%= ChainView.format_usd_value(lp_token_balance_usd) %> +
+ <% else %> + <%= if @token_balance.token.usd_value do %> ++ <%= ChainView.format_usd_value(Chain.balance_in_usd(@token_balance)) %> +
<% end %> - -
+ |
+
+ Asset
+ |
+
+ Type
+ |
+
+ Amount
+ |
+
+ Symbol
+ |
+
+ Price
+ |
+
+ Value
+ |
+
+ Contract Address
+ |
+
---|
<%= format_according_to_decimals(@circles_total_balance, Decimal.new(18)) %> CRC
-<% end %> -<%= if Enum.any?(@token_balances) do %> - +<%= format_according_to_decimals(@circles_total_balance, Decimal.new(18)) %> CRC
+ <% end %> + <%= if Enum.any?(@token_balances) do %> + - - <%= tokens_count_title(@token_balances) %> - <%= if @token_balances && Decimal.cmp(address_tokens_usd_sum_cache(@address_hash, @token_balances), Decimal.new(0)) == :gt do %> - ( >) - <% end %> - -<% else %> - <%= tokens_count_title(@token_balances) %> -<% end %> - -