parent
7f2efcfe34
commit
52845654df
@ -0,0 +1,67 @@ |
|||||||
|
defmodule BlockScoutWeb.SmartContractController do |
||||||
|
use BlockScoutWeb, :controller |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.SmartContract.Reader |
||||||
|
|
||||||
|
def index(conn, %{"hash" => address_hash_string}) do |
||||||
|
with true <- ajax?(conn), |
||||||
|
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), |
||||||
|
{:ok, address} <- Chain.find_contract_address(address_hash) do |
||||||
|
read_only_functions = Reader.read_only_functions(address_hash) |
||||||
|
|
||||||
|
conn |
||||||
|
|> put_status(200) |
||||||
|
|> put_layout(false) |
||||||
|
|> render( |
||||||
|
"_functions.html", |
||||||
|
read_only_functions: read_only_functions, |
||||||
|
address: address |
||||||
|
) |
||||||
|
else |
||||||
|
:error -> |
||||||
|
unprocessable_entity(conn) |
||||||
|
|
||||||
|
{:error, :not_found} -> |
||||||
|
not_found(conn) |
||||||
|
|
||||||
|
_ -> |
||||||
|
not_found(conn) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def show(conn, params) do |
||||||
|
with true <- ajax?(conn), |
||||||
|
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), |
||||||
|
outputs = |
||||||
|
Reader.query_function( |
||||||
|
address_hash, |
||||||
|
%{name: params["function_name"], args: params["args"]} |
||||||
|
) do |
||||||
|
conn |
||||||
|
|> put_status(200) |
||||||
|
|> put_layout(false) |
||||||
|
|> render( |
||||||
|
"_function_response.html", |
||||||
|
function_name: params["function_name"], |
||||||
|
outputs: outputs |
||||||
|
) |
||||||
|
else |
||||||
|
:error -> |
||||||
|
unprocessable_entity(conn) |
||||||
|
|
||||||
|
{:error, :not_found} -> |
||||||
|
not_found(conn) |
||||||
|
|
||||||
|
_ -> |
||||||
|
not_found(conn) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp ajax?(conn) do |
||||||
|
case get_req_header(conn, "x-requested-with") do |
||||||
|
[value] -> value in ["XMLHttpRequest", "xmlhttprequest"] |
||||||
|
[] -> false |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,8 @@ |
|||||||
|
<div class="tile tile-muted monospace"> |
||||||
|
[ <%= @function_name %> method Response ] |
||||||
|
|
||||||
|
<%= for item <- @outputs do %> |
||||||
|
<i class="fa fa-angle-double-right"></i> |
||||||
|
<span class="text-dark"> <%= item["type"] %> : <%= item["value"] %> </span> |
||||||
|
<% end %> |
||||||
|
</div> |
@ -0,0 +1,50 @@ |
|||||||
|
<%= for {function, counter} <- Enum.with_index(@read_only_functions, 1) do %> |
||||||
|
<div class="d-flex py-2 border-bottom" data-function> |
||||||
|
<div class="py-2 pr-2 text-nowrap"> |
||||||
|
<%= counter %>. |
||||||
|
|
||||||
|
<%= function["name"] %> |
||||||
|
|
||||||
|
→ |
||||||
|
</div> |
||||||
|
|
||||||
|
<%= if queryable?(function["inputs"]) do %> |
||||||
|
<div class=""> |
||||||
|
<form class="form-inline" data-function-form data-url="<%= smart_contract_path(@conn, :show, :en, @address.hash) %>"> |
||||||
|
<input type="hidden" name="function_name" value='<%= function["name"] %>' /> |
||||||
|
|
||||||
|
<%= for input <- function["inputs"] do %> |
||||||
|
<div class="form-group pr-2"> |
||||||
|
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm mt-2" placeholder='<%= input["name"] %>(<%= input["type"] %>)' /> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<input type="submit" value='<%= gettext("Query")%>' class="button button--secondary button--xsmall py-0 mt-2" /> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'> |
||||||
|
<%= if (queryable?(function["inputs"])), do: raw "↳" %> |
||||||
|
|
||||||
|
<%= for output <- function["outputs"] do %> |
||||||
|
<%= output["type"] %> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div data-function-response></div> |
||||||
|
</div> |
||||||
|
<% else %> |
||||||
|
<span class="py-2"> |
||||||
|
<%= for output <- function["outputs"] do %> |
||||||
|
<%= if address?(output["type"]) do %> |
||||||
|
<%= link( |
||||||
|
output["value"], |
||||||
|
to: address_path(@conn, :show, @conn.assigns.locale, output["value"]) |
||||||
|
) %> |
||||||
|
<% else %> |
||||||
|
<%= output["value"] %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
</span> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,7 @@ |
|||||||
|
defmodule BlockScoutWeb.SmartContractView do |
||||||
|
use BlockScoutWeb, :view |
||||||
|
|
||||||
|
def queryable?(inputs), do: Enum.any?(inputs) |
||||||
|
|
||||||
|
def address?(type), do: type == "address" |
||||||
|
end |
@ -1,24 +1,43 @@ |
|||||||
defmodule BlockScoutWeb.AddressReadContractControllerTest do |
defmodule BlockScoutWeb.AddressReadContractControllerTest do |
||||||
use BlockScoutWeb.ConnCase |
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
describe "GET show/3" do |
alias Explorer.ExchangeRates.Token |
||||||
test "only responds to ajax requests", %{conn: conn} do |
|
||||||
smart_contract = insert(:smart_contract) |
describe "GET index/3" do |
||||||
|
test "with invalid address hash", %{conn: conn} do |
||||||
path = |
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, "invalid_address")) |
||||||
address_read_contract_path( |
|
||||||
BlockScoutWeb.Endpoint, |
assert html_response(conn, 404) |
||||||
:show, |
end |
||||||
:en, |
|
||||||
smart_contract.address_hash, |
test "with valid address that is not a contract", %{conn: conn} do |
||||||
smart_contract.address_hash, |
address = insert(:address) |
||||||
function_name: "get", |
|
||||||
args: [] |
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, address.hash)) |
||||||
|
|
||||||
|
assert html_response(conn, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "successfully renders the page when the address is a contract", %{conn: conn} do |
||||||
|
contract_address = insert(:contract_address) |
||||||
|
|
||||||
|
transaction = insert(:transaction, from_address: contract_address) |
||||||
|
|
||||||
|
insert( |
||||||
|
:internal_transaction_create, |
||||||
|
index: 0, |
||||||
|
transaction: transaction, |
||||||
|
created_contract_address: contract_address |
||||||
) |
) |
||||||
|
|
||||||
conn = get(conn, path) |
insert(:smart_contract, address_hash: contract_address.hash) |
||||||
|
|
||||||
|
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, contract_address.hash)) |
||||||
|
|
||||||
assert conn.status == 404 |
assert html_response(conn, 200) |
||||||
|
assert contract_address.hash == conn.assigns.address.hash |
||||||
|
assert %Token{} = conn.assigns.exchange_rate |
||||||
|
assert conn.assigns.transaction_count |
||||||
end |
end |
||||||
end |
end |
||||||
end |
end |
||||||
|
@ -0,0 +1,97 @@ |
|||||||
|
defmodule BlockScoutWeb.SmartContractControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
import Mox |
||||||
|
|
||||||
|
setup :verify_on_exit! |
||||||
|
|
||||||
|
describe "GET index/3" do |
||||||
|
test "only responds to ajax requests", %{conn: conn} do |
||||||
|
smart_contract = insert(:smart_contract) |
||||||
|
|
||||||
|
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, :en, hash: smart_contract.address_hash) |
||||||
|
|
||||||
|
conn = get(conn, path) |
||||||
|
|
||||||
|
assert conn.status == 404 |
||||||
|
end |
||||||
|
|
||||||
|
test "lists the smart contract read only functions" do |
||||||
|
token_contract_address = insert(:contract_address) |
||||||
|
|
||||||
|
insert(:smart_contract, address_hash: token_contract_address.hash) |
||||||
|
|
||||||
|
blockchain_get_function_mock() |
||||||
|
|
||||||
|
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, :en, hash: token_contract_address.hash) |
||||||
|
|
||||||
|
conn = |
||||||
|
build_conn() |
||||||
|
|> put_req_header("x-requested-with", "xmlhttprequest") |
||||||
|
|> get(path) |
||||||
|
|
||||||
|
assert conn.status == 200 |
||||||
|
refute conn.assigns.read_only_functions == [] |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "GET show/3" do |
||||||
|
test "only responds to ajax requests", %{conn: conn} do |
||||||
|
smart_contract = insert(:smart_contract) |
||||||
|
|
||||||
|
path = |
||||||
|
smart_contract_path( |
||||||
|
BlockScoutWeb.Endpoint, |
||||||
|
:show, |
||||||
|
:en, |
||||||
|
smart_contract.address_hash, |
||||||
|
function_name: "get", |
||||||
|
args: [] |
||||||
|
) |
||||||
|
|
||||||
|
conn = get(conn, path) |
||||||
|
|
||||||
|
assert conn.status == 404 |
||||||
|
end |
||||||
|
|
||||||
|
test "fetch the function value from the blockchain" do |
||||||
|
smart_contract = insert(:smart_contract) |
||||||
|
|
||||||
|
blockchain_get_function_mock() |
||||||
|
|
||||||
|
path = |
||||||
|
smart_contract_path( |
||||||
|
BlockScoutWeb.Endpoint, |
||||||
|
:show, |
||||||
|
:en, |
||||||
|
smart_contract.address_hash, |
||||||
|
function_name: "get", |
||||||
|
args: [] |
||||||
|
) |
||||||
|
|
||||||
|
conn = |
||||||
|
build_conn() |
||||||
|
|> put_req_header("x-requested-with", "xmlhttprequest") |
||||||
|
|> get(path) |
||||||
|
|
||||||
|
assert conn.status == 200 |
||||||
|
|
||||||
|
assert %{ |
||||||
|
function_name: "get", |
||||||
|
layout: false, |
||||||
|
locale: "en", |
||||||
|
outputs: [%{"name" => "", "type" => "uint256", "value" => 0}] |
||||||
|
} = conn.assigns |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp blockchain_get_function_mock() do |
||||||
|
expect( |
||||||
|
EthereumJSONRPC.Mox, |
||||||
|
:json_rpc, |
||||||
|
fn [%{id: id, method: _, params: [%{data: _, to: _}]}], _options -> |
||||||
|
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000000"}]} |
||||||
|
end |
||||||
|
) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,37 @@ |
|||||||
|
defmodule BlockScoutWeb.Tokens.ReadContractControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
describe "GET index/3" do |
||||||
|
test "with invalid address hash", %{conn: conn} do |
||||||
|
conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, "invalid_address")) |
||||||
|
|
||||||
|
assert html_response(conn, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "successfully renders the page when the token is a verified smart contract", %{conn: conn} do |
||||||
|
token_contract_address = insert(:contract_address) |
||||||
|
|
||||||
|
token = insert(:token, contract_address: token_contract_address) |
||||||
|
|
||||||
|
transaction = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
insert( |
||||||
|
:token_transfer, |
||||||
|
to_address: build(:address), |
||||||
|
transaction: transaction, |
||||||
|
token_contract_address: token_contract_address, |
||||||
|
token: token |
||||||
|
) |
||||||
|
|
||||||
|
conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, token.contract_address_hash)) |
||||||
|
|
||||||
|
assert html_response(conn, 200) |
||||||
|
assert token.contract_address_hash == conn.assigns.token.contract_address_hash |
||||||
|
assert conn.assigns.total_token_transfers |
||||||
|
assert conn.assigns.total_address_in_token_transfers |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue