diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 26edbc4103..d43b267c9b 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -5,8 +5,10 @@ :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:1 -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() lib/block_scout_web/router.ex:1 @@ -21,7 +23,7 @@ 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 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index b59521c32d..e9b3a8fa5d 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -38,7 +38,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -98,7 +98,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -122,7 +122,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -145,7 +145,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ 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_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" @@ -185,7 +185,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -211,7 +211,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -239,7 +239,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -288,7 +288,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -350,7 +350,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -406,7 +406,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -473,7 +473,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -534,7 +534,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_3-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 930e257192..9d73c0ca4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#4745](https://github.com/blockscout/blockscout/pull/4745) - Vyper contracts verification - [#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 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 a55f9ee4f4..15c520a565 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 171f350fae..afed29818d 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -77,7 +77,8 @@ }, "../../../deps/phoenix": { "version": "1.5.13", - "integrity": "sha512-aJKbnuCTtgH8qT7AzHhPwhOP3bqhja3ogFMQmUdY7jNzl/91nsUtpnz0M3HDW7j+IvfjiM2/TIkOaGwzW9BKhg==" + "integrity": "sha512-aJKbnuCTtgH8qT7AzHhPwhOP3bqhja3ogFMQmUdY7jNzl/91nsUtpnz0M3HDW7j+IvfjiM2/TIkOaGwzW9BKhg==", + "license": "MIT" }, "../../../deps/phoenix_html": { "version": "2.14.3", 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 408df30794..ccb9c12fc2 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 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 2427d82aed..e36ef050f5 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 0000000000..f1cb3832e2 --- /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/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 362b146a0e..fe3be0807e 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 @@ -6,7 +6,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 @@ -196,6 +197,40 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end 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) + + 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.") + + {: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 @@ -416,6 +451,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 07b1714141..59431742ad 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/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index c0ef9edf86..c01023faad 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -2505,6 +2505,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:

+
+
+
+
+
+ curl --location --request POST 'http://localhost:4000/api?module=contract&action=verify_vyper_contract' \ + --form 'contractSourceCode="SOURCE_CODE"' \ + --form 'name="Vyper_contract"' \ + --form 'addressHash="0xE60B1B8bD493569a3E945be50A6c89d29a560Fa1"' \ + --form 'compilerVersion="v0.2.12"' + +
+
+
+ """, + required_params: [ + %{ + key: "addressHash", + placeholder: "addressHash", + type: "string", + description: "The address of the contract." + }, + %{ + key: "name", + placeholder: "name", + type: "string", + description: "The name of the contract." + }, + %{ + key: "compilerVersion", + placeholder: "compilerVersion", + type: "string", + description: "The compiler version for the contract." + }, + %{ + key: "contractSourceCode", + placeholder: "contractSourceCode", + type: "string", + description: "The source code of the contract." + } + ], + optional_params: [ + %{ + key: "constructorArguments", + type: "string", + description: "The constructor argument data provided." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verify_example_value), + type: "model", + model: @contract_model + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_verify_example_value_error) + } + ] + } @contract_getabi_action %{ name: "getabi", description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", @@ -2750,7 +2820,8 @@ defmodule BlockScoutWeb.Etherscan do @contract_getabi_action, @contract_getsourcecode_action, @contract_verify_action, - @contract_verify_via_sourcify_action + @contract_verify_via_sourcify_action, + @contract_verify_vyper_contract_action ] } diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index b74d2f9b1d..f3d5b22ba3 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.Notifier do alias BlockScoutWeb.{ AddressContractVerificationViaFlattenedCodeView, AddressContractVerificationViaJsonView, + AddressContractVerificationVyperView, Endpoint } @@ -17,7 +18,7 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token - alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} alias Phoenix.View def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do @@ -47,6 +48,8 @@ defmodule BlockScoutWeb.Notifier do {:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}} ) do verification_from_json_upload? = Map.has_key?(conn.params, "file") + verification_from_flattened_source? = Map.has_key?(conn.params, "external_libraries") + compiler = if verification_from_flattened_source?, do: :solc, else: :vyper contract_verification_result = case contract_verification_result do @@ -55,7 +58,7 @@ defmodule BlockScoutWeb.Notifier do {:error, changeset} -> compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(compiler) do {:ok, compiler_versions} -> compiler_versions @@ -64,10 +67,10 @@ defmodule BlockScoutWeb.Notifier do end view = - if verification_from_json_upload? do - AddressContractVerificationViaJsonView - else - AddressContractVerificationViaFlattenedCodeView + cond do + verification_from_json_upload? -> AddressContractVerificationViaJsonView + verification_from_flattened_source? -> AddressContractVerificationViaFlattenedCodeView + true -> AddressContractVerificationVyperView end result = diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 53d16f1e32..3c194b07bf 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -51,7 +51,7 @@




<%= gettext "Optimization enabled" %>
-
<%= format_optimization_text(target_contract.optimization) %>
+
<%= if target_contract.is_vyper_contract, do: "N/A", else: format_optimization_text(target_contract.optimization) %>
<%= gettext "Compiler version" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex index 51795dfb93..626058e18c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex @@ -26,18 +26,25 @@
- <%= label f, "Verification via" %> + <%= label f, "Verify" %>
<%= radio_button f, :verify_via, true, checked: true, class: "form-check-input verify-via-flattened-code", "aria-describedby": "verify_via-help-block" %>
- <%= label :verify_via, :true, gettext("Flattened source code"), class: "radio-text" %> + <%= label :verify_via, :true, gettext("Via flattened source code"), class: "radio-text" %>
+ <%= if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do %> +
+ <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %> +
+ <%= label :verify_via, :false, gettext("Via Sourcify: Sources and metadata JSON file"), class: "radio-text" %> +
+ <% end %>
- <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %> + <%= radio_button f, :verify_via, false, class: "form-check-input verify-vyper-contract" %>
- <%= label :verify_via, :false, gettext("Sourcify: Sources and metadata JSON file"), class: "radio-text" %> + <%= label :verify_via, :false, gettext("Vyper contract"), class: "radio-text" %>
<%= error_tag f, :verify_via, id: "verify_via-help-block", class: "text-danger form-error" %> @@ -52,7 +59,9 @@
2. Verification through Sourcify.
a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the repo
- b) otherwise you will be asked to upload source files and JSON metadata file(s).
+ b) otherwise you will be asked to upload source files and JSON metadata file(s).
+ 3. Verification of Vyper contract. +
@@ -86,6 +95,14 @@ style: "display: none;", "data-button-loading": "animation" ) %> + <%= link( + gettext("Next"), + to: address_verify_vyper_contract_path(@conn, :new, @address_hash), + id: "verify_vyper_contract_button", + class: "btn-full-primary mr-2", + style: "display: none;", + "data-button-loading": "animation" + ) %> <%= link( gettext("Cancel"), diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex index dfa2f7e91b..501729afab 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex @@ -15,7 +15,7 @@
-

<%= gettext "New Smart Contract Verification" %>

+

<%= gettext "New Solidity Smart Contract Verification" %>

<%= form_for @changeset, address_contract_verification_path(@conn, :create), diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex new file mode 100644 index 0000000000..d4624055c0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex @@ -0,0 +1,102 @@ +<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% contract_name_value = if metadata_for_verification, do: metadata_for_verification.name, else: "Vyper_contract" %> +<% compiler_version = if metadata_for_verification, do: metadata_for_verification.compiler_version, else: "latest" %> +<% contract_source_code_value = if metadata_for_verification, do: metadata_for_verification.contract_source_code, else: "" %> +
+ + +
+

<%= gettext "New Vyper Smart Contract Verification" %>

+ + <%= form_for @changeset, + address_contract_verification_path(@conn, :create), + [], + fn f -> %> + +
+
+ <%= label f, :address_hash, gettext("Contract Address") %> +
+ <%= text_input f, :address_hash, class: "form-control border-rounded", "aria-describedby": "contract-address-help-block", readonly: true %> + <%= error_tag f, :address_hash, id: "contract-address-help-block", class: "text-danger form-error" %> +
+
The 0x address supplied on contract creation.
+
+
+ +
+
+ <%= label f, :name, gettext("Contract Name") %> +
+ <%= text_input f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name", value: contract_name_value, disabled: "true" %> + <%= error_tag f, :name, id: "contract-name-help-block", class: "text-danger form-error" %> +
+
Must match the name specified in the code.
+
+
+ +
+
+ <%= label f, :compiler_version, gettext("Compiler") %> +
+ <%= select f, :compiler_version, @compiler_versions, class: "form-control border-rounded", selected: compiler_version, "aria-describedby": "compiler-help-block" %> + <%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %> +
+
+
+
+ +
+
+ <%= label f, :contract_source_code, gettext("Enter the Vyper Contract Code") %> +
+ <%= textarea f, :contract_source_code, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-source-code-help-block", value: contract_source_code_value %> + <%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger form-error", "data-test": "contract-source-code-error" %> +
+
+
+
+ +
+
+ <%= label f, :constructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %> +
+ <%= textarea f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %> + <%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %> +
+
Add arguments in ABI hex encoded form. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be parsed here.
+
+
+ +
+ + <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %> + <%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> + <%= + link( + gettext("Cancel"), + class: "btn-no-border", + to: address_contract_path(@conn, :index, @address_hash) + ) + %> +
+ <% end %> +
+ +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex index 21ec3c2ef3..90d009f214 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -6,13 +6,7 @@ <%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @log.transaction do %> <% %{to_address: %{hash: hash}} -> %> - <% path = - if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - address_verify_contract_path(@conn, :new, hash) - else - address_verify_contract_via_flattened_code_path(@conn, :new, hash) - end - %> + <% path = address_verify_contract_path(@conn, :new, hash) %> <%= gettext "Verify the contract " %><%= gettext "here" %> <% _ -> %> <%= nil %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index eb2f5c9187..6d21273458 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -219,6 +219,7 @@ @view_module != Elixir.BlockScoutWeb.AddressContractVerificationView && @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaJsonView && @view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView && + @view_module != Elixir.BlockScoutWeb.AddressContractVerificationVyperView && @view_module != Elixir.BlockScoutWeb.AddressReadContractView && @view_module != Elixir.BlockScoutWeb.AddressReadProxyView && @view_module != Elixir.BlockScoutWeb.AddressWriteContractView && diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex index 24ac148187..13b4607b24 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -5,13 +5,7 @@ <%= if minimal_proxy_template do %> <%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: metadata_for_verification.address_hash, conn: @conn %> <% else %> - <% path = - if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - address_verify_contract_path(@conn, :new, @address.hash) - else - address_verify_contract_via_flattened_code_path(@conn, :new, @address.hash) - end - %> + <% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<%= render BlockScoutWeb.CommonComponentsView, "_info.html" %> <%= gettext("Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB") %> <%= link( metadata_for_verification.address_hash, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex index 184f647568..bf85407d05 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex @@ -8,13 +8,7 @@ <%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @transaction do %> <% %{to_address: %{hash: hash}} -> %> - <% path = - if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - address_verify_contract_path(@conn, :new, hash) - else - address_verify_contract_via_flattened_code_path(@conn, :new, hash) - end - %> + <% path = address_verify_contract_path(@conn, :new, hash) %> <%= gettext "Verify the contract " %><%= gettext "here" %> <% _ -> %> <%= nil %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex index b44ae8d919..7141a37293 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex @@ -6,13 +6,7 @@ <%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @transaction do %> <% %{to_address: %{hash: hash}} -> %> - <% path = - if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - address_verify_contract_path(@conn, :new, hash) - else - address_verify_contract_via_flattened_code_path(@conn, :new, hash) - end - %> + <% path = address_verify_contract_path(@conn, :new, hash) %> <%= gettext "Verify the contract " %><%= gettext "here" %> <% _ -> %> <%= nil %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex new file mode 100644 index 0000000000..ea99d78b71 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.AddressContractVerificationVyperView do + use BlockScoutWeb, :view + + alias Explorer.Chain +end diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex index 2f199f2de8..ab8d6c9132 100644 --- a/apps/block_scout_web/lib/block_scout_web/web_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex @@ -132,22 +132,6 @@ defmodule BlockScoutWeb.WebRouter do as: :verify_contract ) - # if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - # resources( - # "/contract_verifications", - # AddressContractVerificationController, - # only: [:new], - # as: :verify_contract - # ) - # else - # resources( - # "/contract_verifications", - # AddressContractVerificationViaFlattenedCodeController, - # only: [:new], - # as: :verify_contract - # ) - # end - resources( "/verify-via-flattened-code", AddressContractVerificationViaFlattenedCodeController, @@ -162,6 +146,13 @@ defmodule BlockScoutWeb.WebRouter do as: :verify_contract_via_json ) + resources( + "/verify-vyper-contract", + AddressContractVerificationVyperController, + only: [:new], + as: :verify_vyper_contract + ) + resources( "/read-contract", AddressReadContractController, diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 5bb63e17c7..75da1b891c 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -125,6 +125,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:156 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:66 msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" @@ -170,7 +171,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:26 lib/block_scout_web/views/address_view.ex:104 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 msgid "Address" msgstr "" @@ -199,7 +200,7 @@ msgid "All" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:18 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:12 msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" @@ -434,9 +435,10 @@ msgid "Call Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:108 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:305 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:56 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:93 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 msgid "Cancel" msgstr "" @@ -526,6 +528,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:44 msgid "Compiler" msgstr "" @@ -552,7 +555,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:13 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:4 lib/block_scout_web/templates/tokens/holder/index.html.eex:17 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:4 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:8 lib/block_scout_web/templates/tokens/holder/index.html.eex:17 msgid "Connection Lost" msgstr "" @@ -596,7 +600,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:27 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:13 lib/block_scout_web/views/address_view.ex:102 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:13 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 lib/block_scout_web/views/address_view.ex:102 msgid "Contract Address" msgstr "" @@ -629,12 +634,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:38 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:33 msgid "Contract Name" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:22 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:10 msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" @@ -817,9 +823,9 @@ msgid "DApp for Staking %{symbol} tokens" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:104 msgid "Data" msgstr "" @@ -840,10 +846,10 @@ msgid "Decimals" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44 lib/block_scout_web/templates/address_logs/_logs.html.eex:59 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:39 lib/block_scout_web/templates/transaction_log/_logs.html.eex:47 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:62 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:33 lib/block_scout_web/templates/transaction_log/_logs.html.eex:41 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 msgid "Decoded" msgstr "" @@ -943,8 +949,8 @@ msgid "ERC-721 " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:95 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:95 lib/block_scout_web/templates/smart_contract/_functions.html.eex:131 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 lib/block_scout_web/templates/smart_contract/_functions.html.eex:125 msgid "ETH" msgstr "" @@ -1047,13 +1053,13 @@ msgid "External libraries" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:39 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:33 msgid "Failed to decode input data." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:41 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Failed to decode log data." msgstr "" @@ -1067,11 +1073,6 @@ msgstr "" msgid "Fetching tokens..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35 -msgid "Flattened source code" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." @@ -1165,7 +1166,7 @@ msgid "However, in general, the" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:25 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" @@ -1340,9 +1341,10 @@ msgid "Loading..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:72 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:299 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:50 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:87 msgid "Loading...." msgstr "" @@ -1352,7 +1354,7 @@ msgid "Log Data" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:120 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:114 msgid "Log Index" msgstr "" @@ -1512,14 +1514,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:9 msgid "New Smart Contract Verification" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:82 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:84 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 lib/block_scout_web/templates/address_contract_verification/new.html.eex:99 msgid "Next" msgstr "" @@ -1672,7 +1673,7 @@ msgid "Potential Reward Share" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:24 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 msgid "Potential matches from our contract method database:" msgstr "" @@ -1710,7 +1711,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:99 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 msgid "Query" msgstr "" @@ -1776,6 +1777,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:302 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:53 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:90 msgid "Reset" msgstr "" @@ -1944,11 +1946,6 @@ msgstr "" msgid "Sources and Metadata JSON" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 -msgid "Sourcify: Sources and metadata JSON file" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:7 msgid "Stake" @@ -2089,7 +2086,7 @@ msgid "The block height of a particular block is defined as the number of blocks msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37 msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" @@ -2129,7 +2126,7 @@ msgid "The percentage of stake in a single pool relative to the total amount sta msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:45 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39 msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" @@ -2330,7 +2327,7 @@ msgid "To" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:26 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" @@ -2436,8 +2433,8 @@ msgid "Topic" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:77 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:80 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:74 msgid "Topics" msgstr "" @@ -2482,7 +2479,7 @@ msgid "Trade STAKE on BitMax" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:25 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 #: lib/block_scout_web/views/transaction_view.ex:463 msgid "Transaction" msgstr "" @@ -2711,19 +2708,20 @@ msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:24 #: lib/block_scout_web/templates/address_contract/index.html.eex:158 lib/block_scout_web/templates/address_contract/index.html.eex:170 #: lib/block_scout_web/templates/address_contract/index.html.eex:201 lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:19 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 msgid "Verify & Publish" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:301 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:52 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:89 msgid "Verify & publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "Verify the contract " msgstr "" @@ -2774,7 +2772,7 @@ msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:130 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:124 msgid "WEI" msgstr "" @@ -2824,7 +2822,7 @@ msgid "Working Stake Amount" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:99 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 msgid "Write" msgstr "" @@ -2939,7 +2937,7 @@ msgid "custom RPC" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37 msgid "fallback" msgstr "" @@ -2949,8 +2947,8 @@ msgid "false" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "here" msgstr "" @@ -2970,7 +2968,7 @@ msgid "of" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:21 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:15 msgid "page" msgstr "" @@ -2980,7 +2978,7 @@ msgid "pool owner" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:45 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39 msgid "receive" msgstr "" @@ -3046,3 +3044,33 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:209 msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:55 +msgid "Enter the Vyper Contract Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 +msgid "New Solidity Smart Contract Verification" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:13 +msgid "New Vyper Smart Contract Verification" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:41 +msgid "Via Sourcify: Sources and metadata JSON file" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35 +msgid "Via flattened source code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:47 +msgid "Vyper contract" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 5bb63e17c7..75da1b891c 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -125,6 +125,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:156 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:66 msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" @@ -170,7 +171,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:26 lib/block_scout_web/views/address_view.ex:104 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 msgid "Address" msgstr "" @@ -199,7 +200,7 @@ msgid "All" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:18 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:12 msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" @@ -434,9 +435,10 @@ msgid "Call Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:108 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:305 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:56 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:93 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 msgid "Cancel" msgstr "" @@ -526,6 +528,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:44 msgid "Compiler" msgstr "" @@ -552,7 +555,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:13 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:4 lib/block_scout_web/templates/tokens/holder/index.html.eex:17 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:4 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:8 lib/block_scout_web/templates/tokens/holder/index.html.eex:17 msgid "Connection Lost" msgstr "" @@ -596,7 +600,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:27 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:13 lib/block_scout_web/views/address_view.ex:102 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:13 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 lib/block_scout_web/views/address_view.ex:102 msgid "Contract Address" msgstr "" @@ -629,12 +634,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:38 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:33 msgid "Contract Name" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:22 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:10 msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" @@ -817,9 +823,9 @@ msgid "DApp for Staking %{symbol} tokens" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:104 msgid "Data" msgstr "" @@ -840,10 +846,10 @@ msgid "Decimals" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44 lib/block_scout_web/templates/address_logs/_logs.html.eex:59 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:39 lib/block_scout_web/templates/transaction_log/_logs.html.eex:47 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:62 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:33 lib/block_scout_web/templates/transaction_log/_logs.html.eex:41 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 msgid "Decoded" msgstr "" @@ -943,8 +949,8 @@ msgid "ERC-721 " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:95 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:95 lib/block_scout_web/templates/smart_contract/_functions.html.eex:131 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 lib/block_scout_web/templates/smart_contract/_functions.html.eex:125 msgid "ETH" msgstr "" @@ -1047,13 +1053,13 @@ msgid "External libraries" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:39 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:33 msgid "Failed to decode input data." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:41 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Failed to decode log data." msgstr "" @@ -1067,11 +1073,6 @@ msgstr "" msgid "Fetching tokens..." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35 -msgid "Flattened source code" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." @@ -1165,7 +1166,7 @@ msgid "However, in general, the" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:25 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" @@ -1340,9 +1341,10 @@ msgid "Loading..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:72 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:299 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:50 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:87 msgid "Loading...." msgstr "" @@ -1352,7 +1354,7 @@ msgid "Log Data" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:120 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:114 msgid "Log Index" msgstr "" @@ -1512,14 +1514,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:9 msgid "New Smart Contract Verification" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:82 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:84 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 lib/block_scout_web/templates/address_contract_verification/new.html.eex:99 msgid "Next" msgstr "" @@ -1672,7 +1673,7 @@ msgid "Potential Reward Share" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:24 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 msgid "Potential matches from our contract method database:" msgstr "" @@ -1710,7 +1711,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:99 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 msgid "Query" msgstr "" @@ -1776,6 +1777,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:302 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:53 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:90 msgid "Reset" msgstr "" @@ -1944,11 +1946,6 @@ msgstr "" msgid "Sources and Metadata JSON" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 -msgid "Sourcify: Sources and metadata JSON file" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:7 msgid "Stake" @@ -2089,7 +2086,7 @@ msgid "The block height of a particular block is defined as the number of blocks msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37 msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" @@ -2129,7 +2126,7 @@ msgid "The percentage of stake in a single pool relative to the total amount sta msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:45 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39 msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" @@ -2330,7 +2327,7 @@ msgid "To" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:26 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" @@ -2436,8 +2433,8 @@ msgid "Topic" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:77 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:80 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:74 msgid "Topics" msgstr "" @@ -2482,7 +2479,7 @@ msgid "Trade STAKE on BitMax" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:25 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 #: lib/block_scout_web/views/transaction_view.ex:463 msgid "Transaction" msgstr "" @@ -2711,19 +2708,20 @@ msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:24 #: lib/block_scout_web/templates/address_contract/index.html.eex:158 lib/block_scout_web/templates/address_contract/index.html.eex:170 #: lib/block_scout_web/templates/address_contract/index.html.eex:201 lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:19 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 msgid "Verify & Publish" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:301 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:52 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:89 msgid "Verify & publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "Verify the contract " msgstr "" @@ -2774,7 +2772,7 @@ msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:130 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:124 msgid "WEI" msgstr "" @@ -2824,7 +2822,7 @@ msgid "Working Stake Amount" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:99 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 msgid "Write" msgstr "" @@ -2939,7 +2937,7 @@ msgid "custom RPC" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37 msgid "fallback" msgstr "" @@ -2949,8 +2947,8 @@ msgid "false" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:16 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 lib/block_scout_web/templates/transaction_log/_logs.html.eex:16 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "here" msgstr "" @@ -2970,7 +2968,7 @@ msgid "of" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:21 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:15 msgid "page" msgstr "" @@ -2980,7 +2978,7 @@ msgid "pool owner" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:45 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39 msgid "receive" msgstr "" @@ -3046,3 +3044,33 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:209 msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:55 +msgid "Enter the Vyper Contract Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:18 +msgid "New Solidity Smart Contract Verification" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:13 +msgid "New Vyper Smart Contract Verification" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:41 +msgid "Via Sourcify: Sources and metadata JSON file" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:35 +msgid "Via flattened source code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:47 +msgid "Vyper contract" +msgstr "" diff --git a/apps/explorer/.gitignore b/apps/explorer/.gitignore index bf75b19355..78db466aba 100644 --- a/apps/explorer/.gitignore +++ b/apps/explorer/.gitignore @@ -1,2 +1,3 @@ priv/.recovery priv/solc_compilers/ +priv/vyper_compilers/ diff --git a/apps/explorer/.sobelow-conf b/apps/explorer/.sobelow-conf index c97cb5e14b..c2b7ff895f 100644 --- a/apps/explorer/.sobelow-conf +++ b/apps/explorer/.sobelow-conf @@ -6,6 +6,7 @@ format: "compact", ignore: ["Config.HTTPS"], ignore_files: [ - "lib/explorer/smart_contract/solidity/code_compiler.ex" + "lib/explorer/smart_contract/solidity/code_compiler.ex", + "lib/explorer/smart_contract/vyper/code_compiler.ex" ] ] diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index b6ee12a58e..c295757a59 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -47,6 +47,7 @@ defmodule Explorer.Application do Supervisor.child_spec({Task.Supervisor, name: Explorer.GenesisDataTaskSupervisor}, id: GenesisDataTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), Explorer.SmartContract.SolcDownloader, + Explorer.SmartContract.VyperDownloader, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Admin.Recovery, [[], [name: Admin.Recovery]]}, TransactionCount, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 66b0b86bf5..fd8a4feb09 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3668,9 +3668,11 @@ defmodule Explorer.Chain do creation_tx_query = from( tx in Transaction, + left_join: a in Address, + on: tx.created_contract_address_hash == a.hash, where: tx.created_contract_address_hash == ^address_hash, where: tx.status == ^1, - select: tx.input + select: %{init: tx.input, created_contract_code: a.contract_code} ) tx_input = @@ -3678,7 +3680,9 @@ defmodule Explorer.Chain do |> Repo.one() if tx_input do - Data.to_string(tx_input) + with %{init: input, created_contract_code: created_contract_code} <- tx_input do + %{init: Data.to_string(input), created_contract_code: Data.to_string(created_contract_code)} + end else creation_int_tx_query = from( @@ -3686,17 +3690,19 @@ defmodule Explorer.Chain do join: t in assoc(itx, :transaction), where: itx.created_contract_address_hash == ^address_hash, where: t.status == ^1, - select: itx.init + select: %{init: itx.init, created_contract_code: itx.created_contract_code} ) - itx_init_code = - creation_int_tx_query - |> Repo.one() + res = creation_int_tx_query |> Repo.one() - if itx_init_code do - Data.to_string(itx_init_code) - else - nil + case res do + %{init: init, created_contract_code: created_contract_code} -> + init_str = Data.to_string(init) + created_contract_code_str = Data.to_string(created_contract_code) + %{init: init_str, created_contract_code: created_contract_code_str} + + _ -> + nil end end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 0df4b02a54..f404178c9b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -194,6 +194,7 @@ defmodule Explorer.Chain.SmartContract do contract. * `verified_via_sourcify` - whether contract verified through Sourcify utility or not. * `partially_verified` - whether contract verified using partial matched source code or not. + * `is_vyper_contract` - boolean flag, determines if contract is Vyper or not """ @type t :: %Explorer.Chain.SmartContract{ @@ -207,7 +208,8 @@ defmodule Explorer.Chain.SmartContract do abi: [function_description], verified_via_sourcify: boolean | nil, partially_verified: boolean | nil, - file_path: String.t() + file_path: String.t(), + is_vyper_contract: boolean | nil } schema "smart_contracts" do @@ -223,6 +225,7 @@ defmodule Explorer.Chain.SmartContract do field(:verified_via_sourcify, :boolean) field(:partially_verified, :boolean) field(:file_path, :string) + field(:is_vyper_contract, :boolean) has_many( :decompiled_smart_contracts, @@ -259,7 +262,8 @@ defmodule Explorer.Chain.SmartContract do :optimization_runs, :verified_via_sourcify, :partially_verified, - :file_path + :file_path, + :is_vyper_contract ]) |> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash]) |> unique_constraint(:address_hash) @@ -280,7 +284,8 @@ defmodule Explorer.Chain.SmartContract do :constructor_arguments, :verified_via_sourcify, :partially_verified, - :file_path + :file_path, + :is_vyper_contract ]) |> validate_required([:name, :compiler_version, :optimization, :address_hash]) diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex new file mode 100644 index 0000000000..d54522c63f --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -0,0 +1,197 @@ +defmodule Explorer.SmartContract.CompilerVersion do + @moduledoc """ + Adapter for fetching compiler versions from https://solc-bin.ethereum.org/bin/list.json. + """ + + @unsupported_solc_versions ~w(0.1.1 0.1.2) + @unsupported_vyper_versions ~w(v0.2.9 v0.2.10) + + @doc """ + Fetches a list of compilers from the Ethereum Solidity API. + """ + @spec fetch_versions(:solc | :vyper) :: {atom, [map]} + def fetch_versions(compiler) do + case compiler do + :solc -> fetch_solc_versions() + :vyper -> fetch_vyper_versions() + end + end + + defp fetch_solc_versions do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.get(source_url(:solc), headers) do + {:ok, %{status_code: 200, body: body}} -> + {:ok, format_data(body, :solc)} + + {:ok, %{status_code: _status_code, body: body}} -> + {:error, decode_json(body)["error"]} + + {:error, %{reason: reason}} -> + {:error, reason} + end + end + + defp fetch_vyper_versions do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.get(source_url(:vyper), headers) do + {:ok, %{status_code: 200, body: body}} -> + {:ok, format_data(body, :vyper)} + + {:ok, %{status_code: _status_code, body: body}} -> + {:error, decode_json(body)["error"]} + + {:error, %{reason: reason}} -> + {:error, reason} + end + end + + @spec vyper_releases_url :: String.t() + def vyper_releases_url do + "https://api.github.com/repos/vyperlang/vyper/releases" + end + + defp format_data(json, compiler) do + versions = + case compiler do + :solc -> + json + |> Jason.decode!() + |> Map.fetch!("builds") + |> remove_unsupported_versions(compiler) + |> format_versions(compiler) + |> Enum.reverse() + + :vyper -> + json + |> Jason.decode!() + |> remove_unsupported_versions(compiler) + |> format_versions(compiler) + |> Enum.sort(fn version1, version2 -> + versions1 = String.split(version1, ".") + versions2 = String.split(version2, ".") + major1 = versions1 |> Enum.at(0) |> parse_integer() + major2 = versions2 |> Enum.at(0) |> parse_integer() + minor1 = versions1 |> Enum.at(1) |> parse_integer() + minor2 = versions2 |> Enum.at(1) |> parse_integer() + patch1 = versions1 |> Enum.at(2) |> String.split("-") |> Enum.at(0) |> parse_integer() + patch2 = versions2 |> Enum.at(2) |> String.split("-") |> Enum.at(0) |> parse_integer() + major1 >= major2 && minor1 >= minor2 && patch1 >= patch2 + end) + end + + ["latest" | versions] + end + + defp parse_integer(string) do + case Integer.parse(string) do + {number, ""} -> number + _ -> nil + end + end + + @spec remove_unsupported_versions([String.t()], :solc | :vyper) :: [String.t()] + defp remove_unsupported_versions(builds, compiler) do + case compiler do + :solc -> + Enum.reject(builds, fn %{"version" => version} -> + Enum.member?(@unsupported_solc_versions, version) + end) + + :vyper -> + Enum.reject(builds, fn %{"tag_name" => version} -> + Enum.member?(@unsupported_vyper_versions, version) + end) + end + end + + defp format_versions(builds, compiler) do + case compiler do + :solc -> + Enum.map(builds, fn build -> + build + |> Map.fetch!("path") + |> String.replace_prefix("soljson-", "") + |> String.replace_suffix(".js", "") + end) + + :vyper -> + Enum.map(builds, fn build -> + build + |> Map.fetch!("tag_name") + end) + end + end + + defp decode_json(json) do + Jason.decode!(json) + end + + @spec source_url(:solc | :vyper) :: String.t() + defp source_url(compiler) do + case compiler do + :solc -> + solc_bin_api_url = Application.get_env(:explorer, :solc_bin_api_url) + "#{solc_bin_api_url}/bin/list.json" + + :vyper -> + vyper_releases_url() + end + end + + def get_strict_compiler_version(compiler, compiler_version) do + case compiler do + :solc -> + if compiler_version == "latest" do + compiler_versions = get_compiler_versions(:solc) + + if Enum.count(compiler_versions) > 1 do + latest_stable_version = + compiler_versions + |> Enum.drop(1) + |> Enum.reduce_while("", fn version, acc -> + if String.contains?(version, "-nightly") do + {:cont, acc} + else + {:halt, version} + end + end) + + latest_stable_version + else + "latest" + end + else + compiler_version + end + + :vyper -> + if compiler_version == "latest" do + compiler_versions = get_compiler_versions(:vyper) + + if Enum.count(compiler_versions) > 1 do + latest_stable_version = + compiler_versions + |> Enum.at(1) + + latest_stable_version + else + "latest" + end + else + compiler_version + end + end + end + + defp get_compiler_versions(compiler) do + case fetch_versions(compiler) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex b/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex index 896dbe58e8..5f63b3189d 100644 --- a/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex +++ b/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex @@ -5,7 +5,7 @@ defmodule Explorer.SmartContract.SolcDownloader do """ use GenServer - alias Explorer.SmartContract.Solidity.CompilerVersion + alias Explorer.SmartContract.CompilerVersion @latest_compiler_refetch_time :timer.minutes(30) @@ -16,7 +16,7 @@ defmodule Explorer.SmartContract.SolcDownloader do path else compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(:solc) do {:ok, compiler_versions} -> compiler_versions diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/solidity/compiler_version.ex deleted file mode 100644 index 91177e147a..0000000000 --- a/apps/explorer/lib/explorer/smart_contract/solidity/compiler_version.ex +++ /dev/null @@ -1,95 +0,0 @@ -defmodule Explorer.SmartContract.Solidity.CompilerVersion do - @moduledoc """ - Adapter for fetching compiler versions from https://solc-bin.ethereum.org/bin/list.json. - """ - - @unsupported_versions ~w(0.1.1 0.1.2) - - @doc """ - Fetches a list of compilers from the Ethereum Solidity API. - """ - @spec fetch_versions :: {atom, [map]} - def fetch_versions do - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.get(source_url(), headers) do - {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body)} - - {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} - - {:error, %{reason: reason}} -> - {:error, reason} - end - end - - defp format_data(json) do - versions = - json - |> Jason.decode!() - |> Map.fetch!("builds") - |> remove_unsupported_versions() - |> format_versions() - |> Enum.reverse() - - ["latest" | versions] - end - - defp remove_unsupported_versions(builds) do - Enum.reject(builds, fn %{"version" => version} -> - Enum.member?(@unsupported_versions, version) - end) - end - - defp format_versions(builds) do - Enum.map(builds, fn build -> - build - |> Map.fetch!("path") - |> String.replace_prefix("soljson-", "") - |> String.replace_suffix(".js", "") - end) - end - - defp decode_json(json) do - Jason.decode!(json) - end - - defp source_url do - solc_bin_api_url = Application.get_env(:explorer, :solc_bin_api_url) - - "#{solc_bin_api_url}/bin/list.json" - end - - def get_strict_compiler_version(compiler_version) do - if compiler_version == "latest" do - compiler_versions = - case fetch_versions() do - {:ok, compiler_versions} -> - compiler_versions - - {:error, _} -> - [] - end - - if Enum.count(compiler_versions) > 1 do - latest_stable_version = - compiler_versions - |> Enum.drop(1) - |> Enum.reduce_while("", fn version, acc -> - if String.contains?(version, "-nightly") do - {:cont, acc} - else - {:halt, version} - end - end) - - latest_stable_version - else - "latest" - end - else - compiler_version - end - end -end diff --git a/apps/explorer/lib/explorer/smart_contract/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex similarity index 93% rename from apps/explorer/lib/explorer/smart_contract/publisher.ex rename to apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index 377fa9ed16..04066bb2af 100644 --- a/apps/explorer/lib/explorer/smart_contract/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -1,18 +1,18 @@ -defmodule Explorer.SmartContract.Publisher do +defmodule Explorer.SmartContract.Solidity.Publisher do @moduledoc """ Module responsible to control the contract verification. """ alias Explorer.Chain alias Explorer.Chain.SmartContract - alias Explorer.SmartContract.Solidity.CompilerVersion - alias Explorer.SmartContract.Verifier + alias Explorer.SmartContract.CompilerVersion + alias Explorer.SmartContract.Solidity.Verifier @doc """ Evaluates smart contract authenticity and saves its details. ## Examples - Explorer.SmartContract.Publisher.publish( + Explorer.SmartContract.Solidity.Publisher.publish( "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", %{ "compiler_version" => "0.4.24", @@ -95,7 +95,7 @@ defmodule Explorer.SmartContract.Publisher do prepared_external_libraries = prepare_external_libraies(params["external_libraries"]) - compiler_version = CompilerVersion.get_strict_compiler_version(params["compiler_version"]) + compiler_version = CompilerVersion.get_strict_compiler_version(:solc, params["compiler_version"]) %{ address_hash: address_hash, @@ -110,7 +110,8 @@ defmodule Explorer.SmartContract.Publisher do secondary_sources: params["secondary_sources"], abi: abi, verified_via_sourcify: params["verified_via_sourcify"], - partially_verified: params["partially_verified"] + partially_verified: params["partially_verified"], + is_vyper_contract: false } end diff --git a/apps/explorer/lib/explorer/smart_contract/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex similarity index 83% rename from apps/explorer/lib/explorer/smart_contract/publisher_worker.ex rename to apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex index f0c248c8fb..0aaa2c768f 100644 --- a/apps/explorer/lib/explorer/smart_contract/publisher_worker.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex @@ -1,4 +1,4 @@ -defmodule Explorer.SmartContract.PublisherWorker do +defmodule Explorer.SmartContract.Solidity.PublisherWorker do @moduledoc """ Background smart contract verification worker. """ @@ -6,7 +6,7 @@ defmodule Explorer.SmartContract.PublisherWorker do use Que.Worker, concurrency: 5 alias Explorer.Chain.Events.Publisher, as: EventsPublisher - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher def perform({address_hash, params, external_libraries, conn}) do result = diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex similarity index 97% rename from apps/explorer/lib/explorer/smart_contract/verifier.ex rename to apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index 6153a4c5f9..09215391af 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -1,5 +1,5 @@ # credo:disable-for-this-file -defmodule Explorer.SmartContract.Verifier do +defmodule Explorer.SmartContract.Solidity.Verifier do @moduledoc """ Module responsible to verify the Smart Contract. @@ -107,12 +107,12 @@ defmodule Explorer.SmartContract.Verifier do blockchain_created_tx_input = case Chain.smart_contract_creation_tx_bytecode(address_hash) do - nil -> - bytecode + %{init: init, created_contract_code: _created_contract_code} -> + "0x" <> init_without_0x = init + init_without_0x - blockchain_created_tx_input_with_0x -> - "0x" <> blockchain_created_tx_input = blockchain_created_tx_input_with_0x - blockchain_created_tx_input + _ -> + bytecode end %{ @@ -177,6 +177,10 @@ defmodule Explorer.SmartContract.Verifier do For more information on the swarm hash, check out: https://solidity.readthedocs.io/en/v0.5.3/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode """ + def extract_bytecode_and_metadata_hash(nil) do + %{"metadata_hash" => nil, "bytecode" => nil, "compiler_version" => nil} + end + def extract_bytecode_and_metadata_hash("0x" <> code) do %{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version} = extract_bytecode_and_metadata_hash(code) diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex new file mode 100644 index 0000000000..ae932cd52d --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/vyper/code_compiler.ex @@ -0,0 +1,74 @@ +defmodule Explorer.SmartContract.Vyper.CodeCompiler do + @moduledoc """ + Module responsible to compile the Vyper code of a given Smart Contract. + """ + + alias Explorer.SmartContract.VyperDownloader + + require Logger + + @spec run(Keyword.t()) :: {:ok, map} | {:error, :compilation | :name} + def run(params) do + compiler_version = Keyword.fetch!(params, :compiler_version) + code = Keyword.fetch!(params, :code) + + path = VyperDownloader.ensure_exists(compiler_version) + + source_file_path = create_source_file(code) + + if path do + {response, _status} = + System.cmd( + path, + [ + "-f", + "abi,bytecode", + source_file_path + ] + ) + + response_data = String.split(response, "\n") + abi_row = response_data |> Enum.at(0) + bytecode = response_data |> Enum.at(1) + + case Jason.decode(abi_row) do + {:ok, abi} -> + {:ok, %{"abi" => abi, "bytecode" => bytecode}} + + {:error, %Jason.DecodeError{}} -> + {:error, :compilation} + end + else + {:error, :compilation} + end + end + + def get_contract_info(contracts, _) when contracts == %{}, do: {:error, :compilation} + + def get_contract_info(contracts, name) do + new_versions_name = ":" <> name + + case contracts do + %{^new_versions_name => response} -> + response + + %{^name => response} -> + response + + _ -> + {:error, :name} + end + end + + def parse_error({:error, %{"error" => error}}), do: {:error, [error]} + def parse_error({:error, %{"errors" => errors}}), do: {:error, errors} + def parse_error({:error, _} = error), do: error + + defp create_source_file(source) do + {:ok, path} = Briefly.create() + + File.write!(path, source) + + path + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex new file mode 100644 index 0000000000..c22590e58c --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex @@ -0,0 +1,69 @@ +defmodule Explorer.SmartContract.Vyper.Publisher do + @moduledoc """ + Module responsible to control Vyper contract verification. + """ + + alias Explorer.Chain + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.CompilerVersion + alias Explorer.SmartContract.Vyper.Verifier + + def publish(address_hash, params) do + case Verifier.evaluate_authenticity(address_hash, params) do + {:ok, %{abi: abi}} -> + publish_smart_contract(address_hash, params, abi) + + {:error, error} -> + {:error, unverified_smart_contract(address_hash, params, error, nil)} + end + end + + def publish_smart_contract(address_hash, params, abi) do + attrs = address_hash |> attributes(params, abi) + + Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) + end + + defp unverified_smart_contract(address_hash, params, error, error_message) do + attrs = attributes(address_hash, params) + + changeset = + SmartContract.invalid_contract_changeset( + %SmartContract{address_hash: address_hash}, + attrs, + error, + error_message + ) + + %{changeset | action: :insert} + end + + defp attributes(address_hash, params, abi \\ %{}) do + constructor_arguments = params["constructor_arguments"] + + clean_constructor_arguments = + if constructor_arguments != nil && constructor_arguments != "" do + constructor_arguments + else + nil + end + + compiler_version = CompilerVersion.get_strict_compiler_version(:vyper, params["compiler_version"]) + + %{ + address_hash: address_hash, + name: "Vyper_contract", + compiler_version: compiler_version, + evm_version: nil, + optimization_runs: nil, + optimization: false, + contract_source_code: params["contract_source_code"], + constructor_arguments: clean_constructor_arguments, + external_libraries: [], + secondary_sources: [], + abi: abi, + verified_via_sourcify: false, + is_vyper_contract: true + } + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex new file mode 100644 index 0000000000..65053596cf --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher_worker.ex @@ -0,0 +1,23 @@ +defmodule Explorer.SmartContract.Vyper.PublisherWorker do + @moduledoc """ + Background smart contract verification worker. + """ + + use Que.Worker, concurrency: 5 + + alias Explorer.Chain.Events.Publisher, as: EventsPublisher + alias Explorer.SmartContract.Vyper.Publisher + + def perform({address_hash, params, conn}) do + result = + case Publisher.publish(address_hash, params) do + {:ok, _contract} = result -> + result + + {:error, changeset} -> + {:error, changeset} + end + + EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex new file mode 100644 index 0000000000..1595223a0f --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex @@ -0,0 +1,65 @@ +# credo:disable-for-this-file +defmodule Explorer.SmartContract.Vyper.Verifier do + @moduledoc """ + Module responsible to verify the Smart Contract through Vyper. + + Given a contract source code the bytecode will be generated and matched + against the existing Creation Address Bytecode, if it matches the contract is + then Verified. + """ + + alias Explorer.Chain + alias Explorer.SmartContract.Vyper.CodeCompiler + + def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name} + + def evaluate_authenticity(_, %{"contract_source_code" => ""}), + do: {:error, :contract_source_code} + + def evaluate_authenticity(address_hash, params) do + verify(address_hash, params) + end + + defp verify(address_hash, params) do + contract_source_code = Map.fetch!(params, "contract_source_code") + compiler_version = Map.fetch!(params, "compiler_version") + constructor_arguments = Map.get(params, "constructor_arguments", "") + + vyper_output = + CodeCompiler.run( + compiler_version: compiler_version, + code: contract_source_code + ) + + compare_bytecodes( + vyper_output, + address_hash, + constructor_arguments + ) + end + + defp compare_bytecodes({:error, _}, _, _), do: {:error, :compilation} + + # credo:disable-for-next-line /Complexity/ + defp compare_bytecodes( + {:ok, %{"abi" => abi, "bytecode" => bytecode}}, + address_hash, + arguments_data + ) do + blockchain_bytecode = + case Chain.smart_contract_creation_tx_bytecode(address_hash) do + %{init: init, created_contract_code: _created_contract_code} -> + init + + _ -> + nil + end + |> String.trim() + + if String.trim(bytecode <> arguments_data) == blockchain_bytecode do + {:ok, %{abi: abi}} + else + {:error, :generated_bytecode} + end + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper_downloader.ex b/apps/explorer/lib/explorer/smart_contract/vyper_downloader.ex new file mode 100644 index 0000000000..dfb873276c --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/vyper_downloader.ex @@ -0,0 +1,127 @@ +defmodule Explorer.SmartContract.VyperDownloader do + @moduledoc """ + Checks to see if the requested Vyper compiler version exists, and if not it + downloads and stores the file. + """ + use GenServer + + alias Explorer.SmartContract.CompilerVersion + + @latest_compiler_refetch_time :timer.minutes(30) + + def ensure_exists(version) do + path = file_path(version) + + if File.exists?(path) && version !== "latest" do + path + else + compiler_versions = + case CompilerVersion.fetch_versions(:vyper) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + + if version in compiler_versions do + GenServer.call(__MODULE__, {:ensure_exists, version}, 60_000) + else + false + end + end + end + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + # sobelow_skip ["Traversal"] + @impl true + def init([]) do + File.mkdir(compiler_dir()) + + {:ok, []} + end + + # sobelow_skip ["Traversal"] + @impl true + def handle_call({:ensure_exists, version}, _from, state) do + path = file_path(version) + + if fetch?(version, path) do + temp_path = file_path("#{version}-tmp") + + contents = download(version) + + file = File.open!(temp_path, [:write, :exclusive]) + + IO.binwrite(file, contents) + File.close(file) + + File.rename(temp_path, path) + System.cmd("chmod", ["+x", path]) + end + + {:reply, path, state} + end + + defp fetch?("latest", path) do + case File.stat(path) do + {:error, :enoent} -> + true + + {:ok, %{mtime: mtime}} -> + last_modified = NaiveDateTime.from_erl!(mtime) + diff = Timex.diff(NaiveDateTime.utc_now(), last_modified, :milliseconds) + + diff > @latest_compiler_refetch_time + end + end + + defp fetch?(_, path) do + not File.exists?(path) + end + + defp file_path(version) do + Path.join(compiler_dir(), "#{version}") + end + + defp compiler_dir do + Application.app_dir(:explorer, "priv/vyper_compilers/") + end + + defp download(version) do + version = CompilerVersion.get_strict_compiler_version(:vyper, version) + releases_path = CompilerVersion.vyper_releases_url() + + releases_body = + releases_path + |> HTTPoison.get!([], timeout: 60_000, recv_timeout: 60_000) + |> Map.get(:body) + |> Jason.decode!() + + release = + releases_body + |> Enum.find(fn release -> + Map.get(release, "tag_name") == version + end) + + release_assets = Map.get(release, "assets") + + download_path = + Enum.reduce_while(release_assets, "", fn asset, acc -> + browser_download_url = Map.get(asset, "browser_download_url") + + if browser_download_url =~ "linux" do + {:halt, browser_download_url} + else + {:cont, acc} + end + end) + + download_path + |> HTTPoison.get!([], timeout: 60_000, recv_timeout: 60_000, follow_redirect: true, hackney: [force_redirect: true]) + |> Map.get(:body) + end +end diff --git a/apps/explorer/priv/repo/migrations/20210616120552_smart_contracts_add_is_vyper_flag.exs b/apps/explorer/priv/repo/migrations/20210616120552_smart_contracts_add_is_vyper_flag.exs new file mode 100644 index 0000000000..171a90d4e4 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20210616120552_smart_contracts_add_is_vyper_flag.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.SmartContractsAddIsVyperFlag do + use Ecto.Migration + + def change do + alter table(:smart_contracts) do + add(:is_vyper_contract, :boolean, null: true) + end + end +end diff --git a/apps/explorer/test/explorer/smart_contract/solidity/compiler_version_test.exs b/apps/explorer/test/explorer/smart_contract/compiler_version_test.exs similarity index 80% rename from apps/explorer/test/explorer/smart_contract/solidity/compiler_version_test.exs rename to apps/explorer/test/explorer/smart_contract/compiler_version_test.exs index 6e36da7255..02e396d866 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/compiler_version_test.exs +++ b/apps/explorer/test/explorer/smart_contract/compiler_version_test.exs @@ -1,12 +1,12 @@ -defmodule Explorer.SmartContract.Solidity.CompilerVersionTest do +defmodule Explorer.SmartContract.CompilerVersionTest do use ExUnit.Case - doctest Explorer.SmartContract.Solidity.CompilerVersion + doctest Explorer.SmartContract.CompilerVersion - alias Explorer.SmartContract.Solidity.CompilerVersion + alias Explorer.SmartContract.CompilerVersion alias Plug.Conn - describe "fetch_versions" do + describe "fetch_versions/1" do setup do bypass = Bypass.open() @@ -23,7 +23,7 @@ defmodule Explorer.SmartContract.Solidity.CompilerVersionTest do Conn.resp(conn, 200, solc_bin_versions()) end) - assert {:ok, versions} = CompilerVersion.fetch_versions() + assert {:ok, versions} = CompilerVersion.fetch_versions(:solc) assert Enum.any?(versions, fn item -> item == "v0.4.9+commit.364da425" end) == true end @@ -35,7 +35,7 @@ defmodule Explorer.SmartContract.Solidity.CompilerVersionTest do Conn.resp(conn, 200, solc_bin_versions()) end) - assert {:ok, versions} = CompilerVersion.fetch_versions() + assert {:ok, versions} = CompilerVersion.fetch_versions(:solc) assert List.first(versions) == "latest" end @@ -44,13 +44,13 @@ defmodule Explorer.SmartContract.Solidity.CompilerVersionTest do Conn.resp(conn, 400, ~S({"error": "bad request"})) end) - assert {:error, "bad request"} = CompilerVersion.fetch_versions() + assert {:error, "bad request"} = CompilerVersion.fetch_versions(:solc) end test "returns error when there is server error", %{bypass: bypass} do Bypass.down(bypass) - assert {:error, :econnrefused} = CompilerVersion.fetch_versions() + assert {:error, :econnrefused} = CompilerVersion.fetch_versions(:solc) end end diff --git a/apps/explorer/test/explorer/smart_contract/publisher_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/publisher_test.exs similarity index 98% rename from apps/explorer/test/explorer/smart_contract/publisher_test.exs rename to apps/explorer/test/explorer/smart_contract/solidity/publisher_test.exs index 368c4d3a87..35ec6c3ca2 100644 --- a/apps/explorer/test/explorer/smart_contract/publisher_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/publisher_test.exs @@ -1,15 +1,15 @@ -defmodule Explorer.SmartContract.PublisherTest do +defmodule Explorer.SmartContract.Solidity.PublisherTest do use ExUnit.Case, async: true use Explorer.DataCase - doctest Explorer.SmartContract.Publisher + doctest Explorer.SmartContract.Solidity.Publisher @moduletag timeout: :infinity alias Explorer.Chain.{ContractMethod, SmartContract} alias Explorer.{Factory, Repo} - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher describe "publish/2" do test "with valid data creates a smart_contract" do @@ -86,7 +86,7 @@ defmodule Explorer.SmartContract.PublisherTest do } response = Publisher.publish(contract_address.hash, valid_attrs) - assert {:ok, %SmartContract{} = smart_contract} = response + assert {:ok, %SmartContract{} = _smart_contract} = response Enum.each(contract_code_info.abi, fn selector -> [parsed] = ABI.parse_specification([selector]) @@ -174,7 +174,7 @@ defmodule Explorer.SmartContract.PublisherTest do end) response = Publisher.publish(contract_address.hash, params, external_libraries_form_params) - assert {:ok, %SmartContract{} = smart_contract} = response + assert {:ok, %SmartContract{} = _smart_contract} = response end end end diff --git a/apps/explorer/test/explorer/smart_contract/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs similarity index 99% rename from apps/explorer/test/explorer/smart_contract/verifier_test.exs rename to apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs index ac170a6cf5..35029b3880 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs @@ -1,12 +1,12 @@ -defmodule Explorer.SmartContract.VerifierTest do +defmodule Explorer.SmartContract.Solidity.VerifierTest do use ExUnit.Case, async: true use Explorer.DataCase @moduletag timeout: :infinity - doctest Explorer.SmartContract.Verifier + doctest Explorer.SmartContract.Solidity.Verifier - alias Explorer.SmartContract.Verifier + alias Explorer.SmartContract.Solidity.Verifier alias Explorer.Factory @code_0_4 """ diff --git a/docker/Dockerfile b/docker/Dockerfile index 2ff694c011..961197c196 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,6 +2,17 @@ FROM bitwalker/alpine-elixir-phoenix:1.12 RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file +ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc +ENV GLIBC_VERSION=2.30-r0 + +RUN set -ex && \ + apk --update add libstdc++ curl ca-certificates && \ + for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ + do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ + apk add --allow-untrusted /tmp/*.apk && \ + rm -v /tmp/*.apk && \ + /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib + # Get Rust RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y