Merge pull request #1596 from poanetwork/ab-create-decompiled-smart-contracts
create decompiled smart contractspull/1647/head
commit
809219de86
@ -0,0 +1,79 @@ |
|||||||
|
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 |
||||||
|
with {:ok, hash} <- validate_address_hash(params["address_hash"]), |
||||||
|
: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)) |
||||||
|
|
||||||
|
{:error, changeset} -> |
||||||
|
errors = |
||||||
|
changeset.errors |
||||||
|
|> Enum.into(%{}, fn {field, {message, _}} -> |
||||||
|
{field, message} |
||||||
|
end) |
||||||
|
|
||||||
|
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"})) |
||||||
|
|
||||||
|
:contract_exists -> |
||||||
|
send_resp( |
||||||
|
conn, |
||||||
|
:unprocessable_entity, |
||||||
|
encode(%{error: "decompiled code already exists for the decompiler version"}) |
||||||
|
) |
||||||
|
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, hash} -> {:ok, hash} |
||||||
|
:error -> :invalid_address |
||||||
|
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 |
||||||
|
other -> other |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp actual_token do |
||||||
|
Application.get_env(:block_scout_web, :decompiled_smart_contract_token) |
||||||
|
end |
||||||
|
|
||||||
|
defp encode(data) do |
||||||
|
Jason.encode!(data) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,99 @@ |
|||||||
|
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 |
||||||
|
assert request.resp_body == "{\"error\":\"address_hash is invalid\"}" |
||||||
|
end |
||||||
|
|
||||||
|
test "returns unprocessable_entity when code is empty", %{conn: conn} do |
||||||
|
decompiler_version = "test_decompiler" |
||||||
|
address = insert(:address) |
||||||
|
|
||||||
|
params = %{ |
||||||
|
"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 == "{\"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" |
||||||
|
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 |
||||||
|
|
||||||
|
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 |
||||||
|
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 |
@ -0,0 +1,33 @@ |
|||||||
|
defmodule Explorer.Chain.DecompiledSmartContract do |
||||||
|
@moduledoc """ |
||||||
|
The representation of a decompiled smart contract. |
||||||
|
""" |
||||||
|
|
||||||
|
use Explorer.Schema |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:address, |
||||||
|
Address, |
||||||
|
foreign_key: :address_hash, |
||||||
|
references: :hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
timestamps() |
||||||
|
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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue