diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex index 797c2427a0..1eec849380 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex @@ -7,7 +7,8 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do def create(conn, params) do if auth_token(conn) == actual_token() do with {:ok, hash} <- validate_address_hash(params["address_hash"]), - :ok <- smart_contract_exists?(hash) do + :ok <- smart_contract_exists?(hash), + :ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do case Chain.create_decompiled_smart_contract(params) do {:ok, decompiled_smart_contract} -> send_resp(conn, :created, Jason.encode!(decompiled_smart_contract)) @@ -22,8 +23,18 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do send_resp(conn, :unprocessable_entity, encode(errors)) end else - :invalid_address -> send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"})) - :not_found -> send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) + :invalid_address -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"})) + + :not_found -> + send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) + + :contract_exists -> + send_resp( + conn, + :unprocessable_entity, + encode(%{error: "decompiled code already exists for the decompiler version"}) + ) end else send_resp(conn, :forbidden, "") @@ -44,6 +55,13 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do end end + defp decompiled_contract_exists?(address_hash, decompiler_version) do + case Chain.decompiled_code(address_hash, decompiler_version) do + {:ok, _} -> :contract_exists + _ -> :ok + end + end + defp auth_token(conn) do case get_req_header(conn, "auth_token") do [token] -> token diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs index bb554f910c..3df8561d0a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs @@ -40,6 +40,30 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do assert request.resp_body == "{\"decompiled_source_code\":\"can't be blank\"}" end + test "can not update code for the same decompiler version", %{conn: conn} do + address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + insert(:decompiled_smart_contract, + address_hash: address_hash, + decompiler_version: decompiler_version, + decompiled_source_code: decompiled_source_code + ) + + params = %{ + "address_hash" => address_hash, + "decompiler_version" => decompiler_version, + "decompiled_source_code" => decompiled_source_code + } + + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) + + assert request.status == 422 + + assert request.resp_body == "{\"error\":\"decompiled code already exists for the decompiler version\"}" + end + test "creates decompiled smart contract", %{conn: conn} do address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) decompiler_version = "test_decompiler" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 0ff52616a5..5860f80eb9 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -668,6 +668,20 @@ defmodule Explorer.Chain do end end + def decompiled_code(address_hash, version) do + query = + from(contract in DecompiledSmartContract, + where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version + ) + + query + |> Repo.one() + |> case do + nil -> {:error, :not_found} + contract -> {:ok, contract.decompiled_source_code} + end + end + @spec token_contract_address_from_token_name(String.t()) :: {:ok, Hash.Address.t()} | {:error, :not_found} def token_contract_address_from_token_name(name) when is_binary(name) do query =