Contracts read/write: method_id instead function_name as a key

pull/3257/head
Victor Baranov 4 years ago
parent 1a98e2d002
commit fe16d056d9
  1. 2
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  2. 3
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  3. 1
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  4. 10
      apps/block_scout_web/priv/gettext/default.pot
  5. 10
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  6. 24
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  7. 147
      apps/explorer/lib/explorer/smart_contract/reader.ex
  8. 2
      apps/explorer/package-lock.json

@ -53,6 +53,7 @@ const readWriteFunction = (element) => {
if (action === 'read') { if (action === 'read') {
const url = $form.data('url') const url = $form.data('url')
const $functionName = $form.find('input[name=function_name]') const $functionName = $form.find('input[name=function_name]')
const $methodId = $form.find('input[name=method_id]')
const $functionInputs = $form.find('input[name=function_input]') const $functionInputs = $form.find('input[name=function_input]')
const args = $.map($functionInputs, element => { const args = $.map($functionInputs, element => {
@ -61,6 +62,7 @@ const readWriteFunction = (element) => {
const data = { const data = {
function_name: $functionName.val(), function_name: $functionName.val(),
method_id: $methodId.val(),
args args
} }

@ -94,7 +94,7 @@ defmodule BlockScoutWeb.SmartContractController do
outputs = outputs =
Reader.query_function( Reader.query_function(
address_hash, address_hash,
%{name: params["function_name"], args: params["args"]}, %{method_id: params["method_id"], args: params["args"]},
contract_type contract_type
) )
@ -104,6 +104,7 @@ defmodule BlockScoutWeb.SmartContractController do
|> render( |> render(
"_function_response.html", "_function_response.html",
function_name: params["function_name"], function_name: params["function_name"],
method_id: params["method_id"],
outputs: outputs outputs: outputs
) )
else else

@ -42,6 +42,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
<%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %>
<form class="form-inline" data-function-form data-action="<%= if writeable?(function), do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= @contract_abi %>" data-implementation-abi="<%= @implementation_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>"> <form class="form-inline" data-function-form data-action="<%= if writeable?(function), do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= @contract_abi %>" data-implementation-abi="<%= @implementation_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>">
<input type="hidden" name="function_name" value='<%= function["name"] %>' /> <input type="hidden" name="function_name" value='<%= function["name"] %>' />
<input type="hidden" name="method_id" value='<%= function["method_id"] %>' />
<%= if queryable?(function["inputs"]) do %> <%= if queryable?(function["inputs"]) do %>
<%= for input <- function["inputs"] do %> <%= for input <- function["inputs"] do %>

@ -562,8 +562,8 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:56 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:57
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:91 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:92
msgid "ETH" msgid "ETH"
msgstr "" msgstr ""
@ -1181,7 +1181,7 @@ msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:60 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:61
msgid "Query" msgid "Query"
msgstr "" msgstr ""
@ -1633,7 +1633,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:90 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:91
msgid "WEI" msgid "WEI"
msgstr "" msgstr ""
@ -1923,7 +1923,7 @@ msgid "Waiting for transaction's confirmation..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:60 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:61
msgid "Write" msgid "Write"
msgstr "" msgstr ""

@ -562,8 +562,8 @@ msgid "ERC-721 "
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:56 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:57
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:91 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:92
msgid "ETH" msgid "ETH"
msgstr "" msgstr ""
@ -1181,7 +1181,7 @@ msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:60 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:61
msgid "Query" msgid "Query"
msgstr "" msgstr ""
@ -1633,7 +1633,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:90 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:91
msgid "WEI" msgid "WEI"
msgstr "" msgstr ""
@ -1923,7 +1923,7 @@ msgid "Waiting for transaction's confirmation..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:60 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:61
msgid "Write" msgid "Write"
msgstr "" msgstr ""

@ -38,13 +38,18 @@ defmodule EthereumJSONRPC.Contract do
indexed_responses = indexed_responses =
requests_with_index requests_with_index
|> Enum.map(fn {%{contract_address: contract_address, function_name: function_name, args: args} = request, index} -> |> Enum.map(fn {%{contract_address: contract_address, method_id: target_method_id, args: args} = request, index} ->
{_, function} = {_, function} =
Enum.find(functions, fn {_method_id, func} -> Enum.find(functions, fn {method_id, _func} ->
func.function == function_name && Enum.count(func.input_names) == Enum.count(args) if method_id do
Base.encode16(method_id, case: :lower) == target_method_id || method_id == target_method_id
else
method_id == target_method_id
end
end) end)
function function
|> Map.drop([:method_id])
|> Encoder.encode_function_call(args) |> Encoder.encode_function_call(args)
|> eth_call_request(contract_address, index, Map.get(request, :block_number), Map.get(request, :from)) |> eth_call_request(contract_address, index, Map.get(request, :block_number), Map.get(request, :from))
end) end)
@ -57,15 +62,22 @@ defmodule EthereumJSONRPC.Contract do
end end
|> Enum.into(%{}, &{&1.id, &1}) |> Enum.into(%{}, &{&1.id, &1})
Enum.map(requests_with_index, fn {%{function_name: function_name}, index} -> Enum.map(requests_with_index, fn {%{method_id: method_id}, index} ->
selectors = Enum.filter(parsed_abi, fn p_abi -> p_abi.function == function_name end)
indexed_responses[index] indexed_responses[index]
|> case do |> case do
nil -> nil ->
{:error, "No result"} {:error, "No result"}
response -> response ->
selectors =
Enum.filter(parsed_abi, fn p_abi ->
if method_id && p_abi.method_id do
Base.encode16(p_abi.method_id, case: :lower) == method_id || p_abi.method_id == method_id
else
p_abi.method_id == method_id
end
end)
{^index, result} = Encoder.decode_result(response, selectors) {^index, result} = Encoder.decode_result(response, selectors)
result result
end end

@ -96,10 +96,10 @@ defmodule Explorer.SmartContract.Reader do
def query_contract(contract_address, abi, functions) do def query_contract(contract_address, abi, functions) do
requests = requests =
functions functions
|> Enum.map(fn {function_name, args} -> |> Enum.map(fn {method_id, args} ->
%{ %{
contract_address: contract_address, contract_address: contract_address,
function_name: function_name, method_id: method_id,
args: args args: args
} }
end) end)
@ -108,7 +108,7 @@ defmodule Explorer.SmartContract.Reader do
|> query_contracts(abi) |> query_contracts(abi)
|> Enum.zip(requests) |> Enum.zip(requests)
|> Enum.into(%{}, fn {response, request} -> |> Enum.into(%{}, fn {response, request} ->
{request.function_name, response} {request.method_id, response}
end) end)
end end
@ -121,11 +121,11 @@ defmodule Explorer.SmartContract.Reader do
def query_contract(contract_address, from, abi, functions) do def query_contract(contract_address, from, abi, functions) do
requests = requests =
functions functions
|> Enum.map(fn {function_name, args} -> |> Enum.map(fn {method_id, args} ->
%{ %{
contract_address: contract_address, contract_address: contract_address,
from: from, from: from,
function_name: function_name, method_id: method_id,
args: args args: args
} }
end) end)
@ -134,7 +134,7 @@ defmodule Explorer.SmartContract.Reader do
|> query_contracts(abi) |> query_contracts(abi)
|> Enum.zip(requests) |> Enum.zip(requests)
|> Enum.into(%{}, fn {response, request} -> |> Enum.into(%{}, fn {response, request} ->
{request.function_name, response} {request.method_id, response}
end) end)
end end
@ -200,9 +200,11 @@ defmodule Explorer.SmartContract.Reader do
[] []
_ -> _ ->
abi abi_with_method_id = get_abi_with_method_id(abi)
abi_with_method_id
|> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view")) |> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view"))
|> Enum.map(&fetch_current_value_from_blockchain(&1, abi, contract_address_hash)) |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash))
end end
end end
@ -214,9 +216,72 @@ defmodule Explorer.SmartContract.Reader do
[] []
_ -> _ ->
implementation_abi implementation_abi_with_method_id = get_abi_with_method_id(implementation_abi)
implementation_abi_with_method_id
|> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view")) |> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view"))
|> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi, contract_address_hash)) |> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi_with_method_id, contract_address_hash))
end
end
defp get_abi_with_method_id(abi) do
parsed_abi =
abi
|> ABI.parse_specification()
abi_with_method_id =
abi
|> Enum.map(fn target_method ->
methods =
parsed_abi
|> Enum.filter(fn method ->
Atom.to_string(method.type) == Map.get(target_method, "type") &&
method.function == Map.get(target_method, "name") &&
Enum.count(method.input_names) == Enum.count(Map.get(target_method, "inputs")) &&
input_types_matched?(method.types, target_method)
end)
if Enum.count(methods) > 0 do
method = Enum.at(methods, 0)
method_id = Map.get(method, :method_id)
method_with_id = Map.put(target_method, "method_id", Base.encode16(method_id, case: :lower))
method_with_id
else
target_method
end
end)
abi_with_method_id
end
defp input_types_matched?(types, target_method) do
Enum.all?(types, fn target_type ->
index = Enum.find_index(types, fn type -> type == target_type end)
type_to_compare = Map.get(Enum.at(Map.get(target_method, "inputs"), index), "type")
target_type_formatted = format_input_type(target_type)
target_type_formatted == type_to_compare
end)
end
defp format_input_type(input_type) do
case input_type do
{:array, {type, size}, array_size} ->
Atom.to_string(type) <> Integer.to_string(size) <> "[" <> Integer.to_string(array_size) <> "]"
{:array, type, array_size} ->
Atom.to_string(type) <> "[" <> Integer.to_string(array_size) <> "]"
{:array, {type, size}} ->
Atom.to_string(type) <> Integer.to_string(size) <> "[]"
{:array, type} ->
Atom.to_string(type) <> "[]"
{type, size} ->
Atom.to_string(type) <> Integer.to_string(size)
type ->
Atom.to_string(type)
end end
end end
@ -224,16 +289,16 @@ defmodule Explorer.SmartContract.Reader do
values = values =
case function do case function do
%{"inputs" => []} -> %{"inputs" => []} ->
name = function["name"] method_id = function["method_id"]
args = function["inputs"] args = function["inputs"]
outputs = function["outputs"] outputs = function["outputs"]
contract_address_hash contract_address_hash
|> query_verified_contract(%{name => normalize_args(args)}, abi) |> query_verified_contract(%{method_id => normalize_args(args)}, abi)
|> link_outputs_and_values(outputs, name) |> link_outputs_and_values(outputs, method_id)
_ -> _ ->
link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["name"]) link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["method_id"])
end end
Map.replace!(function, "outputs", values) Map.replace!(function, "outputs", values)
@ -242,13 +307,13 @@ defmodule Explorer.SmartContract.Reader do
@doc """ @doc """
Fetches the blockchain value of a function that requires arguments. Fetches the blockchain value of a function that requires arguments.
""" """
@spec query_function(String.t(), %{name: String.t(), args: nil}, atom()) :: [%{}] @spec query_function(String.t(), %{method_id: String.t(), args: nil}, atom()) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: nil}, type) do def query_function(contract_address_hash, %{method_id: method_id, args: nil}, type) do
query_function(contract_address_hash, %{name: name, args: []}, type) query_function(contract_address_hash, %{method_id: method_id, args: []}, type)
end end
@spec query_function(Hash.t(), %{name: String.t(), args: [term()]}, atom()) :: [%{}] @spec query_function(Hash.t(), %{method_id: String.t(), args: [term()]}, atom()) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: args}, type) do def query_function(contract_address_hash, %{method_id: method_id, args: args}, type) do
abi = abi =
contract_address_hash contract_address_hash
|> Chain.address_hash_to_smart_contract() |> Chain.address_hash_to_smart_contract()
@ -261,23 +326,47 @@ defmodule Explorer.SmartContract.Reader do
abi abi
end end
outputs = parsed_final_abi =
case final_abi do final_abi
|> ABI.parse_specification()
%{outputs: outputs, method_id: method_id} =
case parsed_final_abi do
nil -> nil ->
nil nil
_ -> _ ->
function = function_object =
final_abi parsed_final_abi
|> Enum.filter(fn function -> function["name"] == name end) |> Enum.filter(fn %ABI.FunctionSelector{method_id: find_method_id} ->
Base.encode16(find_method_id, case: :lower) == method_id
end)
|> List.first() |> List.first()
function["outputs"] %ABI.FunctionSelector{returns: returns, method_id: method_id} = function_object
outputs = extract_outputs(returns)
%{outputs: outputs, method_id: method_id}
end end
contract_address_hash contract_address_hash
|> query_verified_contract(%{name => normalize_args(args)}, final_abi) |> query_verified_contract(%{method_id => normalize_args(args)}, final_abi)
|> link_outputs_and_values(outputs, name) |> link_outputs_and_values(outputs, method_id)
end
defp extract_outputs(returns) do
returns
|> Enum.map(fn output ->
case output do
{type, size} ->
full_type = Atom.to_string(type) <> Integer.to_string(size)
%{"type" => full_type}
type ->
%{"type" => type}
end
end)
end end
@doc """ @doc """
@ -308,9 +397,9 @@ defmodule Explorer.SmartContract.Reader do
end end
end end
def link_outputs_and_values(blockchain_values, outputs, function_name) do def link_outputs_and_values(blockchain_values, outputs, method_id) do
default_value = Enum.map(outputs, fn _ -> "" end) default_value = Enum.map(outputs, fn _ -> "" end)
{_, value} = Map.get(blockchain_values, function_name, {:ok, default_value}) {_, value} = Map.get(blockchain_values, method_id, {:ok, default_value})
for {output, index} <- Enum.with_index(outputs) do for {output, index} <- Enum.with_index(outputs) do
new_value(output, List.wrap(value), index) new_value(output, List.wrap(value), index)

@ -125,7 +125,7 @@
}, },
"os-tmpdir": { "os-tmpdir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
}, },
"path-is-absolute": { "path-is-absolute": {

Loading…
Cancel
Save