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 |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
describe "GET show/3" do |
||||
test "only responds to ajax requests", %{conn: conn} do |
||||
smart_contract = insert(:smart_contract) |
||||
|
||||
path = |
||||
address_read_contract_path( |
||||
BlockScoutWeb.Endpoint, |
||||
:show, |
||||
:en, |
||||
smart_contract.address_hash, |
||||
smart_contract.address_hash, |
||||
function_name: "get", |
||||
args: [] |
||||
) |
||||
|
||||
conn = get(conn, path) |
||||
|
||||
assert conn.status == 404 |
||||
alias Explorer.ExchangeRates.Token |
||||
|
||||
describe "GET index/3" do |
||||
test "with invalid address hash", %{conn: conn} do |
||||
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, "invalid_address")) |
||||
|
||||
assert html_response(conn, 404) |
||||
end |
||||
|
||||
test "with valid address that is not a contract", %{conn: conn} do |
||||
address = insert(:address) |
||||
|
||||
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 |
||||
) |
||||
|
||||
insert(:smart_contract, address_hash: contract_address.hash) |
||||
|
||||
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, :en, contract_address.hash)) |
||||
|
||||
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 |
||||
|
@ -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