From 667394b6e743f339457f98874b1db6fee4abb6cc Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 19 Mar 2019 12:54:57 +0300 Subject: [PATCH 01/15] create decompiled smart contracts --- .../chain/decompiled_smart_contract.ex | 25 +++++++++++++++++++ ...1821_create_decompiled_smart_contracts.exs | 15 +++++++++++ .../chain/decompiled_smart_contract_test.exs | 20 +++++++++++++++ apps/explorer/test/support/factory.ex | 17 ++++++++++--- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex create mode 100644 apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs create mode 100644 apps/explorer/test/explorer/chain/decompiled_smart_contract_test.exs diff --git a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex new file mode 100644 index 0000000000..99cea2e3cc --- /dev/null +++ b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex @@ -0,0 +1,25 @@ +defmodule Explorer.Chain.DecompiledSmartContract do + use Explorer.Schema + + alias Explorer.Chain.{Address, Hash} + + schema "decompiled_smart_contracts" do + field(:decompiler_version, :string) + field(:decompiled_source_code, :string) + + belongs_to( + :address, + Address, + foreign_key: :address_hash, + references: :hash, + type: Hash.Address + ) + end + + def changeset(%__MODULE__{} = smart_contract, attrs) do + smart_contract + |> cast(attrs, [:decompiler_version, :decompiled_source_code, :address_hash]) + |> validate_required([:decompiler_version, :decompiled_source_code, :address_hash]) + |> unique_constraint(:address_hash) + end +end diff --git a/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs new file mode 100644 index 0000000000..4055f3242b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Migrations.CreateDecompiledSmartContracts do + use Ecto.Migration + + def change do + create table(:decompiled_smart_contracts) do + add(:decompiler_version, :string, null: false) + add(:decompiled_source_code, :text, null: false) + add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false) + + timestamps() + end + + create(unique_index(:decompiled_smart_contracts, :address_hash)) + end +end diff --git a/apps/explorer/test/explorer/chain/decompiled_smart_contract_test.exs b/apps/explorer/test/explorer/chain/decompiled_smart_contract_test.exs new file mode 100644 index 0000000000..f305161763 --- /dev/null +++ b/apps/explorer/test/explorer/chain/decompiled_smart_contract_test.exs @@ -0,0 +1,20 @@ +defmodule Explorer.Chain.DecompiledSmartContractTest do + use Explorer.DataCase + + alias Explorer.Chain.DecompiledSmartContract + + describe "changeset/2" do + test "with valid attributes" do + params = params_for(:decompiled_smart_contract) + changeset = DecompiledSmartContract.changeset(%DecompiledSmartContract{}, params) + + assert changeset.valid? + end + + test "with invalid attributes" do + changeset = DecompiledSmartContract.changeset(%DecompiledSmartContract{}, %{elixir: "erlang"}) + + refute changeset.valid? + end + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index f5bf521738..6a800b3006 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -19,6 +19,7 @@ defmodule Explorer.Factory do Block, ContractMethod, Data, + DecompiledSmartContract, Hash, InternalTransaction, Log, @@ -509,7 +510,7 @@ defmodule Explorer.Factory do } end - def smart_contract_factory() do + def smart_contract_factory do contract_code_info = contract_code_info() %SmartContract{ @@ -522,7 +523,17 @@ defmodule Explorer.Factory do } end - def token_balance_factory() do + def decompiled_smart_contract_factory do + contract_code_info = contract_code_info() + + %DecompiledSmartContract{ + address_hash: insert(:address, contract_code: contract_code_info.bytecode).hash, + decompiler_version: "test_decompiler", + decompiled_source_code: contract_code_info.source_code + } + end + + def token_balance_factory do %TokenBalance{ address: build(:address), token_contract_address_hash: insert(:token).contract_address_hash, @@ -532,7 +543,7 @@ defmodule Explorer.Factory do } end - def address_current_token_balance_factory() do + def address_current_token_balance_factory do %CurrentTokenBalance{ address: build(:address), token_contract_address_hash: insert(:token).contract_address_hash, From bac51ee1831fd710f2461cdeb40c8f85da3516bb Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 19 Mar 2019 14:55:12 +0300 Subject: [PATCH 02/15] add endpoint to create decompiled smart contracts --- .../lib/block_scout_web/router.ex | 2 ++ apps/explorer/lib/explorer/chain.ex | 12 +++++++++ .../chain/decompiled_smart_contract.ex | 2 ++ apps/explorer/test/explorer/chain_test.exs | 26 +++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index cf40492b28..d13ff1a85e 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -22,6 +22,8 @@ defmodule BlockScoutWeb.Router do pipe_through(:api) get("/supply", SupplyController, :supply) + + resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create]) end scope "/api", BlockScoutWeb.API.RPC do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index a28b6aac96..fbce4407d6 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -31,6 +31,7 @@ defmodule Explorer.Chain do Block, BlockNumberCache, Data, + DecompiledSmartContract, Hash, Import, InternalTransaction, @@ -512,6 +513,17 @@ defmodule Explorer.Chain do |> Repo.insert() end + @doc """ + Creates a decompiled smart contract. + """ + + @spec create_decompiled_smart_contract(map()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()} + def create_decompiled_smart_contract(attrs) do + %DecompiledSmartContract{} + |> DecompiledSmartContract.changeset(attrs) + |> Repo.insert() + end + @doc """ Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently. diff --git a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex index 99cea2e3cc..4b509b7ec0 100644 --- a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex @@ -14,6 +14,8 @@ defmodule Explorer.Chain.DecompiledSmartContract do references: :hash, type: Hash.Address ) + + timestamps() end def changeset(%__MODULE__{} = smart_contract, attrs) do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 259e20e05e..899e187714 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2586,6 +2586,32 @@ defmodule Explorer.ChainTest do end end + describe "create_decompiled_smart_contract/1" do + test "with valid params creates decompiled smart contract" do + address_hash = to_string(insert(:address).hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + params = %{ + address_hash: address_hash, + decompiler_version: decompiler_version, + decompiled_source_code: decompiled_source_code + } + + {:ok, decompiled_smart_contract} = Chain.create_decompiled_smart_contract(params) + + assert decompiled_smart_contract.decompiler_version == decompiler_version + assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code + assert address_hash == to_string(decompiled_smart_contract.address_hash) + end + + test "with invalid params can't create decompiled smart contract" do + params = %{code: "cat"} + + {:error, _changeset} = Chain.create_decompiled_smart_contract(params) + end + end + describe "create_smart_contract/1" do setup do smart_contract_bytecode = From fd18b89aafaaff24b9307a790befd8c04125330b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 19 Mar 2019 15:29:39 +0300 Subject: [PATCH 03/15] add auth --- apps/block_scout_web/config/config.exs | 3 +- .../decompiled_smart_contract_controller.ex | 30 ++++++++++ ...ompiled_smart_contract_controller_test.exs | 56 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 2379cc02c9..179e83c8c4 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -10,7 +10,8 @@ config :block_scout_web, namespace: BlockScoutWeb, ecto_repos: [Explorer.Repo], version: System.get_env("BLOCKSCOUT_VERSION"), - release_link: System.get_env("RELEASE_LINK") + release_link: System.get_env("RELEASE_LINK"), + decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN") config :block_scout_web, BlockScoutWeb.Chain, network: System.get_env("NETWORK"), 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 new file mode 100644 index 0000000000..cde85a9915 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex @@ -0,0 +1,30 @@ +defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do + use BlockScoutWeb, :controller + + alias Explorer.Chain + + def create(conn, params) do + if auth_token(conn) == actual_token() do + case Chain.create_decompiled_smart_contract(params) do + {:ok, _decompiled_source_code} -> + put_status(conn, :created) + + {:error, _changeset} -> + put_status(conn, :unprocessable_entity) + end + else + put_status(conn, :forbidden) + end + end + + defp auth_token(conn) do + case get_req_header(conn, "auth_token") do + [token] -> token + other -> other + end + end + + defp actual_token do + Application.get_env(:block_scout_web, :decompiled_smart_contract_token) + end +end 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 new file mode 100644 index 0000000000..a2b0dbde76 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs @@ -0,0 +1,56 @@ +defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Repo + alias Explorer.Chain.DecompiledSmartContract + + import Ecto.Query, + only: [from: 2] + + @secret "secret" + + describe "when used authorized" do + setup %{conn: conn} = context do + Application.put_env(:block_scout_web, :decompiled_smart_contract_token, @secret) + + auth_conn = conn |> put_req_header("auth_token", @secret) + + {:ok, Map.put(context, :conn, auth_conn)} + end + + test "returns unprocessable_entity status when params are invalid", %{conn: conn} do + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) + + assert request.status == 422 + end + + test "creates decompiled smart contract", %{conn: conn} do + address_hash = to_string(insert(:address).hash) + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + 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 == 201 + + decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash)) + assert to_string(decompiled_smart_contract.address_hash) == address_hash + assert decompiled_smart_contract.decompiler_version == decompiler_version + assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code + end + end + + describe "when user is not authorized" do + test "returns forbedden", %{conn: conn} do + request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) + + assert request.status == 403 + end + end +end From a7559c1f27ac2ee3031a30bb725712c7c6ca8ccd Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 19 Mar 2019 15:34:19 +0300 Subject: [PATCH 04/15] add moduledoc --- apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex index 4b509b7ec0..6ad2a37da0 100644 --- a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex @@ -1,4 +1,8 @@ defmodule Explorer.Chain.DecompiledSmartContract do + @moduledoc """ + The representation of a decompiled smart contract. + """ + use Explorer.Schema alias Explorer.Chain.{Address, Hash} From 9c1442dfe35ee636fcd808053227fc74123f3f79 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 22 Mar 2019 14:29:46 +0300 Subject: [PATCH 05/15] send response --- .../api/v1/decompiled_smart_contract_controller.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 cde85a9915..fa1541fd3d 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,13 +7,13 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do if auth_token(conn) == actual_token() do case Chain.create_decompiled_smart_contract(params) do {:ok, _decompiled_source_code} -> - put_status(conn, :created) + send_resp(conn, :created, "") {:error, _changeset} -> - put_status(conn, :unprocessable_entity) + send_resp(conn, :unprocessable_entity, "") end else - put_status(conn, :forbidden) + send_resp(conn, :forbidden, "") end end From f01c52e0d8b99524fd53c30f7e7edb50b45c7ce2 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 22 Mar 2019 14:58:36 +0300 Subject: [PATCH 06/15] add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fcd748491..684ed983e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ### Features - [1611](https://github.com/poanetwork/blockscout/pull/1611) - allow setting the first indexing block + - [1596](https://github.com/poanetwork/blockscout/pull/1596) - add endpoint to create decompiled contracts + ### Fixes From 88a32bb984404df40be404d4dabc56acba33d832 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 09:21:31 +0300 Subject: [PATCH 07/15] add upsert for decompied smart contacts --- apps/explorer/lib/explorer/chain.ex | 2 +- apps/explorer/test/explorer/chain_test.exs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6d0519e5c9..5eccb89c19 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -521,7 +521,7 @@ defmodule Explorer.Chain do def create_decompiled_smart_contract(attrs) do %DecompiledSmartContract{} |> DecompiledSmartContract.changeset(attrs) - |> Repo.insert() + |> Repo.insert(on_conflict: :replace_all, conflict_target: :address_hash) end @doc """ diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 899e187714..ff5e4307a4 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -14,6 +14,7 @@ defmodule Explorer.ChainTest do Address, Block, Data, + DecompiledSmartContract, Hash, InternalTransaction, Log, @@ -2610,6 +2611,27 @@ defmodule Explorer.ChainTest do {:error, _changeset} = Chain.create_decompiled_smart_contract(params) end + + test "update smart contract" do + inserted_decompiled_smart_contract = insert(:decompiled_smart_contract) + version = "2" + code = "code2" + + {:ok, _decompiled_smart_contract} = + Chain.create_decompiled_smart_contract(%{ + decompiler_version: version, + decompiled_source_code: code, + address_hash: inserted_decompiled_smart_contract.address_hash + }) + + decompiled_smart_contract = + Repo.one( + from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash) + ) + + assert decompiled_smart_contract.decompiled_source_code == code + assert decompiled_smart_contract.decompiler_version == version + end end describe "create_smart_contract/1" do From ead333610c20ef48490874bccbd20a75e91f0fbd Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 09:47:24 +0300 Subject: [PATCH 08/15] handle invalid address_hash --- .../decompiled_smart_contract_controller.ex | 22 ++++++++++++++----- ...ompiled_smart_contract_controller_test.exs | 15 +++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) 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 fa1541fd3d..b0ebc5b40e 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 @@ -2,21 +2,33 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do use BlockScoutWeb, :controller alias Explorer.Chain + alias Explorer.Chain.Hash.Address def create(conn, params) do if auth_token(conn) == actual_token() do - case Chain.create_decompiled_smart_contract(params) do - {:ok, _decompiled_source_code} -> - send_resp(conn, :created, "") + with :ok <- validate_address_hash(params["address_hash"]) do + case Chain.create_decompiled_smart_contract(params) do + {:ok, _decompiled_source_code} -> + send_resp(conn, :created, "") - {:error, _changeset} -> - send_resp(conn, :unprocessable_entity, "") + {:error, _changeset} -> + send_resp(conn, :unprocessable_entity, "") + end + else + :error -> send_resp(conn, :unprocessable_entity, "") end else send_resp(conn, :forbidden, "") end end + defp validate_address_hash(address_hash) do + case Address.cast(address_hash) do + {:ok, _} -> :ok + :error -> :error + 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 a2b0dbde76..3aa6eb5387 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 @@ -24,6 +24,21 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do assert request.status == 422 end + test "returns unprocessable_entity when address_hash is invalid", %{conn: conn} do + decompiler_version = "test_decompiler" + decompiled_source_code = "hello world" + + params = %{ + "address_hash" => "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 + end + test "creates decompiled smart contract", %{conn: conn} do address_hash = to_string(insert(:address).hash) decompiler_version = "test_decompiler" From 3ef8293dedf57109ab7d0ddfee0c380e4fb8dce6 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 10:04:48 +0300 Subject: [PATCH 09/15] add response bodies --- .../api/v1/decompiled_smart_contract_controller.ex | 6 +++--- .../api/v1/decompiled_smart_contract_controller_test.exs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) 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 b0ebc5b40e..ab71f6c910 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 @@ -9,13 +9,13 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do with :ok <- validate_address_hash(params["address_hash"]) do case Chain.create_decompiled_smart_contract(params) do {:ok, _decompiled_source_code} -> - send_resp(conn, :created, "") + send_resp(conn, :created, "ok") {:error, _changeset} -> - send_resp(conn, :unprocessable_entity, "") + send_resp(conn, :unprocessable_entity, "error") end else - :error -> send_resp(conn, :unprocessable_entity, "") + :error -> send_resp(conn, :unprocessable_entity, "error") end else send_resp(conn, :forbidden, "") 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 3aa6eb5387..72a54010bd 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 @@ -22,6 +22,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) assert request.status == 422 + assert request.resp_body == "error" end test "returns unprocessable_entity when address_hash is invalid", %{conn: conn} do @@ -37,6 +38,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) assert request.status == 422 + assert request.resp_body == "error" end test "creates decompiled smart contract", %{conn: conn} do @@ -53,6 +55,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) assert request.status == 201 + assert request.resp_body == "ok" decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash)) assert to_string(decompiled_smart_contract.address_hash) == address_hash From 3244d54cf7139ba18c1da68590acc00e2037088f Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 11:01:22 +0300 Subject: [PATCH 10/15] allow creation different smart contract for address_hash --- apps/explorer/lib/explorer/chain.ex | 2 +- ...1821_create_decompiled_smart_contracts.exs | 4 ++- apps/explorer/test/explorer/chain_test.exs | 32 ++++++++++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5eccb89c19..0ff52616a5 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -521,7 +521,7 @@ defmodule Explorer.Chain do def create_decompiled_smart_contract(attrs) do %DecompiledSmartContract{} |> DecompiledSmartContract.changeset(attrs) - |> Repo.insert(on_conflict: :replace_all, conflict_target: :address_hash) + |> Repo.insert(on_conflict: :replace_all, conflict_target: [:decompiler_version, :address_hash]) end @doc """ diff --git a/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs index 4055f3242b..98d0c15108 100644 --- a/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs +++ b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs @@ -10,6 +10,8 @@ defmodule Explorer.Repo.Migrations.CreateDecompiledSmartContracts do timestamps() end - create(unique_index(:decompiled_smart_contracts, :address_hash)) + create( + unique_index(:decompiled_smart_contracts, [:address_hash, :decompiler_version], name: :address_decompiler_version) + ) end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index ff5e4307a4..78607c4fa9 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2612,25 +2612,47 @@ defmodule Explorer.ChainTest do {:error, _changeset} = Chain.create_decompiled_smart_contract(params) end - test "update smart contract" do + test "updates smart contract code" do inserted_decompiled_smart_contract = insert(:decompiled_smart_contract) - version = "2" code = "code2" {:ok, _decompiled_smart_contract} = Chain.create_decompiled_smart_contract(%{ - decompiler_version: version, + decompiler_version: inserted_decompiled_smart_contract.decompiler_version, decompiled_source_code: code, address_hash: inserted_decompiled_smart_contract.address_hash }) decompiled_smart_contract = Repo.one( - from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash) + from(ds in DecompiledSmartContract, + where: + ds.address_hash == ^inserted_decompiled_smart_contract.address_hash and + ds.decompiler_version == ^inserted_decompiled_smart_contract.decompiler_version + ) ) assert decompiled_smart_contract.decompiled_source_code == code - assert decompiled_smart_contract.decompiler_version == version + end + + test "creates two smart contracts for different decompiler versions" do + inserted_decompiled_smart_contract = insert(:decompiled_smart_contract) + code = "code2" + version = "2" + + {:ok, _decompiled_smart_contract} = + Chain.create_decompiled_smart_contract(%{ + decompiler_version: version, + decompiled_source_code: code, + address_hash: inserted_decompiled_smart_contract.address_hash + }) + + decompiled_smart_contracts = + Repo.all( + from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash) + ) + + assert Enum.count(decompiled_smart_contracts) == 2 end end From 77d717069e101a1974cf827ea46c4b396d8f2352 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 11:20:42 +0300 Subject: [PATCH 11/15] create separate migration --- ...190319081821_create_decompiled_smart_contracts.exs | 4 +--- ...emove_unique_address_hash_decompiled_contracts.exs | 11 +++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs diff --git a/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs index 98d0c15108..4055f3242b 100644 --- a/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs +++ b/apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs @@ -10,8 +10,6 @@ defmodule Explorer.Repo.Migrations.CreateDecompiledSmartContracts do timestamps() end - create( - unique_index(:decompiled_smart_contracts, [:address_hash, :decompiler_version], name: :address_decompiler_version) - ) + create(unique_index(:decompiled_smart_contracts, :address_hash)) end end diff --git a/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs b/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs new file mode 100644 index 0000000000..863de15085 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.RemoveUniqueAddressHashDecompiledContracts do + use Ecto.Migration + + def change do + drop index(:decompiled_smart_contracts, [:address_hash]) + + create( + unique_index(:decompiled_smart_contracts, [:address_hash, :decompiler_version], name: :address_decompiler_version) + ) + end +end From 3f96b9c2b802a54d1de4dc1fed1719931078ba50 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 11:27:21 +0300 Subject: [PATCH 12/15] mix format --- ...25081658_remove_unique_address_hash_decompiled_contracts.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs b/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs index 863de15085..eb4b94237e 100644 --- a/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs +++ b/apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs @@ -2,7 +2,7 @@ defmodule Explorer.Repo.Migrations.RemoveUniqueAddressHashDecompiledContracts do use Ecto.Migration def change do - drop index(:decompiled_smart_contracts, [:address_hash]) + drop(index(:decompiled_smart_contracts, [:address_hash])) create( unique_index(:decompiled_smart_contracts, [:address_hash, :decompiler_version], name: :address_decompiler_version) From e9560967029f6e5cec66c812eee0ae2befa71cce Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 13:05:47 +0300 Subject: [PATCH 13/15] return full responses --- .../decompiled_smart_contract_controller.ex | 36 ++++++++++++++----- ...ompiled_smart_contract_controller_test.exs | 19 +++++----- .../chain/decompiled_smart_contract.ex | 2 ++ apps/explorer/lib/explorer/chain/hash.ex | 10 ++++++ 4 files changed, 50 insertions(+), 17 deletions(-) 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 ab71f6c910..4b690cad0b 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 @@ -6,26 +6,42 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do def create(conn, params) do if auth_token(conn) == actual_token() do - with :ok <- validate_address_hash(params["address_hash"]) do + with {:ok, hash} <- validate_address_hash(params["address_hash"]), + :ok <- smart_contract_exists?(hash) do case Chain.create_decompiled_smart_contract(params) do - {:ok, _decompiled_source_code} -> - send_resp(conn, :created, "ok") + {:ok, decompiled_smart_contract} -> + send_resp(conn, :created, Jason.encode!(decompiled_smart_contract)) - {:error, _changeset} -> - send_resp(conn, :unprocessable_entity, "error") + {:error, changeset} -> + errors = + changeset.errors + |> Enum.map(fn {field, {message, _}} -> + {field, message} + end) + |> Enum.into(%{}) + + send_resp(conn, :unprocessable_entity, encode(errors)) end else - :error -> send_resp(conn, :unprocessable_entity, "error") + :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"})) end else send_resp(conn, :forbidden, "") end end + defp smart_contract_exists?(address_hash) do + case Chain.hash_to_address(address_hash) do + {:ok, _address} -> :ok + _ -> :not_found + end + end + defp validate_address_hash(address_hash) do case Address.cast(address_hash) do - {:ok, _} -> :ok - :error -> :error + {:ok, hash} -> {:ok, hash} + :error -> :invalid_address end end @@ -39,4 +55,8 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do defp actual_token do Application.get_env(:block_scout_web, :decompiled_smart_contract_token) end + + defp encode(data) do + Jason.encode!(data) + end end 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 72a54010bd..bb554f910c 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 @@ -22,27 +22,26 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create)) assert request.status == 422 - assert request.resp_body == "error" + assert request.resp_body == "{\"error\":\"address_hash is invalid\"}" end - test "returns unprocessable_entity when address_hash is invalid", %{conn: conn} do + test "returns unprocessable_entity when code is empty", %{conn: conn} do decompiler_version = "test_decompiler" - decompiled_source_code = "hello world" + address = insert(:address) params = %{ - "address_hash" => "hash", - "decompiler_version" => decompiler_version, - "decompiled_source_code" => decompiled_source_code + "address_hash" => to_string(address.hash), + "decompiler_version" => decompiler_version } request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) assert request.status == 422 - assert request.resp_body == "error" + assert request.resp_body == "{\"decompiled_source_code\":\"can't be blank\"}" end test "creates decompiled smart contract", %{conn: conn} do - address_hash = to_string(insert(:address).hash) + address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash) decompiler_version = "test_decompiler" decompiled_source_code = "hello world" @@ -55,7 +54,9 @@ defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params) assert request.status == 201 - assert request.resp_body == "ok" + + assert request.resp_body == + "{\"address_hash\":\"0x0000000000000000000000000000000000000001\",\"decompiler_version\":\"test_decompiler\",\"decompiled_source_code\":\"hello world\"}" decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash)) assert to_string(decompiled_smart_contract.address_hash) == address_hash diff --git a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex index 6ad2a37da0..7952146044 100644 --- a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex @@ -7,6 +7,8 @@ defmodule Explorer.Chain.DecompiledSmartContract do alias Explorer.Chain.{Address, Hash} + @derive {Jason.Encoder, only: [:address_hash, :decompiler_version, :decompiled_source_code]} + schema "decompiled_smart_contracts" do field(:decompiler_version, :string) field(:decompiled_source_code, :string) diff --git a/apps/explorer/lib/explorer/chain/hash.ex b/apps/explorer/lib/explorer/chain/hash.ex index ed02b5ac53..64e67aabec 100644 --- a/apps/explorer/lib/explorer/chain/hash.ex +++ b/apps/explorer/lib/explorer/chain/hash.ex @@ -233,4 +233,14 @@ defmodule Explorer.Chain.Hash do |> BitString.encode(options) end end + + defimpl Jason.Encoder do + alias Jason.Encode + + def encode(hash, opts) do + hash + |> to_string() + |> Encode.string(opts) + end + end end From 26fb7ca944711c1b826c1bfb220539b10b6562cb Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 13:43:39 +0300 Subject: [PATCH 14/15] mix format --- .../controllers/api/v1/decompiled_smart_contract_controller.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 4b690cad0b..797c2427a0 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 @@ -15,10 +15,9 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do {:error, changeset} -> errors = changeset.errors - |> Enum.map(fn {field, {message, _}} -> + |> Enum.into(%{}, fn {field, {message, _}} -> {field, message} end) - |> Enum.into(%{}) send_resp(conn, :unprocessable_entity, encode(errors)) end From e76ab75d5b0746cda97bf23e5f3767e2895e3438 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 25 Mar 2019 14:48:43 +0300 Subject: [PATCH 15/15] do not allow update code --- .../decompiled_smart_contract_controller.ex | 24 ++++++++++++++++--- ...ompiled_smart_contract_controller_test.exs | 24 +++++++++++++++++++ apps/explorer/lib/explorer/chain.ex | 14 +++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) 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 =