diff --git a/CHANGELOG.md b/CHANGELOG.md index dd90a14a0b..09e0bf65c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features ### Fixes +- [#5025](https://github.com/blockscout/blockscout/pull/5025) - Add standard input JSON files validation ### Chore diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index adae681600..adb077990e 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -9,6 +9,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do @new_contract_name "New.sol" + @required_standard_input_fields ~w(language sources settings) + + @default_output_selection %{"*" => %{"*" => ["*"]}} + @doc """ Compiles a code in the solidity command line. @@ -132,24 +136,24 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do path = SolcDownloader.ensure_exists(compiler_version) if path do - {response, _status} = - System.cmd( - "node", - [ - Application.app_dir(:explorer, "priv/compile_solc_standard_json_input.js"), - create_source_file(json_input), - path - ] - ) - - with {:ok, decoded} <- Jason.decode(response), + with {:ok, valid_json} <- tune_json(json_input), + {response, _status} <- + System.cmd( + "node", + [ + Application.app_dir(:explorer, "priv/compile_solc_standard_json_input.js"), + create_source_file(valid_json), + path + ] + ), + {:ok, decoded} <- Jason.decode(response), {:ok, contracts} <- get_contracts_standard_input_verification(decoded) do fetch_candidates(contracts, name) else {:error, %Jason.DecodeError{}} -> {:error, :compilation} - {:error, reason} when reason in [:name, :compilation] -> + {:error, reason} when reason in [:name, :compilation, :json] -> {:error, reason} error -> @@ -164,6 +168,27 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do end end + defp tune_json(json_input) when is_binary(json_input) do + case Jason.decode(json_input) do + {:ok, map_input} -> + map_set_input_keys = map_input |> Map.keys() |> MapSet.new() + map_set_required_keys = MapSet.new(@required_standard_input_fields) + + if MapSet.subset?(map_set_required_keys, map_set_input_keys) do + settings = Map.fetch!(map_input, "settings") + new_settings = Map.put(settings, "outputSelection", @default_output_selection) + map_input |> Map.replace("settings", new_settings) |> Jason.encode() + else + {:error, :json} + end + + _ -> + {:error, :json} + end + end + + defp tune_json(_json_input), do: {:error, :json} + defp fetch_candidates(contracts, "") when is_map(contracts) do candidates = for {file, content} <- contracts,