Add sender's address for read-only functions; Done some smart-contract endpoints

pull/6642/head
Никита Поздняков 2 years ago
parent 41474d53b9
commit 8b50f6d3f1
No known key found for this signature in database
GPG Key ID: F344106F9804FE5F
  1. 14
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  2. 9
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  3. 150
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  4. 8
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  6. 114
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  7. 1
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  8. 18
      apps/explorer/lib/explorer/smart_contract/reader.ex

@ -1,10 +1,10 @@
import $ from 'jquery'
import { connectSelector, disconnectSelector, getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers'
import { connectSelector, disconnectSelector, getCurrentAccountPromise, getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers'
import { queryMethod, callMethod } from './interact'
import { walletEnabled, connectToWallet, disconnectWallet, web3ModalInit } from './connect.js'
import '../../pages/address'
const loadFunctions = (element, isCustomABI) => {
const loadFunctions = (element, isCustomABI, from) => {
const $element = $(element)
const url = $element.data('url')
const hash = $element.data('hash')
@ -13,7 +13,7 @@ const loadFunctions = (element, isCustomABI) => {
$.get(
url,
{ hash, type, action, is_custom_abi: isCustomABI },
{ hash, type, action, is_custom_abi: isCustomABI, from: from },
response => $element.html(response)
)
.done(function () {
@ -96,11 +96,15 @@ const readWriteFunction = (element) => {
const container = $('[data-smart-contract-functions]')
if (container.length) {
loadFunctions(container, false)
getCurrentAccountPromise(window.web3 && window.web3.currentProvider).then((currentAccount) => {
loadFunctions(container, false, currentAccount)}, () => {loadFunctions(container, false, null)
})
}
const customABIContainer = $('[data-smart-contract-functions-custom]')
if (customABIContainer.length) {
loadFunctions(customABIContainer, true)
getCurrentAccountPromise(window.web3 && window.web3.currentProvider).then((currentAccount) => {
loadFunctions(container, false, currentAccount)}, () => {loadFunctions(container, true, null)
})
}

@ -129,6 +129,15 @@ defmodule BlockScoutWeb.ApiRouter do
get("/:address_hash/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day)
end
scope "/smart-contracts" do
get("/:address_hash", V2.AddressController, :smart_contract)
get("/:address_hash/methods-read", V2.AddressController, :methods_read)
get("/:address_hash/methods-write", V2.AddressController, :methods_write)
get("/:address_hash/methods-read-proxy", V2.AddressController, :methods_read_proxy)
get("/:address_hash/methods-write-proxy", V2.AddressController, :methods_write_proxy)
get("/:address_hash/query-read-method", V2.AddressController, :query_read_method)
end
scope "/tokens" do
get("/:address_hash", V2.TokenController, :token)
get("/:address_hash/counters", V2.TokenController, :counters)

@ -12,9 +12,15 @@ defmodule BlockScoutWeb.API.V2.AddressController do
import BlockScoutWeb.PagingHelper,
only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1]
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
alias BlockScoutWeb.AccessHelpers
alias BlockScoutWeb.API.V2.{AddressView, BlockView, TransactionView}
alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController
alias BlockScoutWeb.AddressView
alias BlockScoutWeb.API.V2.{BlockView, TransactionView}
alias Explorer.{Chain, Market}
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{Reader, Writer}
alias Indexer.Fetcher.TokenBalanceOnDemand
@transaction_necessity_by_association [
@ -37,6 +43,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do
}
]
@smart_contract_address_options [
necessity_by_association: %{
:smart_contract => :optional
}
]
@burn_address "0x0000000000000000000000000000000000000000"
action_fallback(BlockScoutWeb.API.V2.FallbackController)
def address(conn, %{"address_hash" => address_hash_string} = params) do
@ -249,7 +263,6 @@ defmodule BlockScoutWeb.API.V2.AddressController do
conn
|> put_status(200)
|> put_view(AddressView)
|> render(:coin_balances, %{coin_balances: coin_balances, next_page_params: next_page_params})
end
end
@ -263,8 +276,139 @@ defmodule BlockScoutWeb.API.V2.AddressController do
conn
|> put_status(200)
|> put_view(AddressView)
|> render(:coin_balances_by_day, %{coin_balances_by_day: balances_by_day})
end
end
def smart_contract(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
_ <- VerificationController.check_and_verify(address_hash_string),
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, true)} do
conn
|> put_status(200)
|> render(:smart_contract, %{address: address})
end
end
def methods_read(conn, %{"address_hash" => address_hash_string, "is_custom_abi" => "true"} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string),
{:not_found, true} <- {:not_found, AddressView.check_custom_abi_for_having_read_functions(custom_abi)} do
read_only_functions_from_abi =
Reader.read_only_functions_from_abi_with_sender(custom_abi.abi, address_hash, params["from"])
read_functions_required_wallet_from_abi = Reader.read_functions_required_wallet_from_abi(custom_abi.abi)
conn
|> put_status(200)
|> json(read_only_functions_from_abi ++ read_functions_required_wallet_from_abi)
end
end
def methods_read(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
read_only_functions_from_abi = Reader.read_only_functions(address_hash, params["from"]) |> debug("r only")
read_functions_required_wallet_from_abi =
Reader.read_functions_required_wallet(address_hash) |> debug("r req wall")
conn
|> put_status(200)
|> json(read_only_functions_from_abi ++ read_functions_required_wallet_from_abi)
end
end
def methods_write(conn, %{"address_hash" => address_hash_string, "is_custom_abi" => "true"} = params) do
with {:format, {:ok, _address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string),
{:not_found, true} <- {:not_found, AddressView.check_custom_abi_for_having_read_functions(custom_abi)} do
conn
|> put_status(200)
|> json(Writer.filter_write_functions(custom_abi.abi))
end
end
def methods_write(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
conn
|> put_status(200)
|> json(Writer.write_functions(address_hash))
end
end
def methods_read_proxy(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)} do
implementation_address_hash_string =
address.smart_contract
|> SmartContract.get_implementation_address_hash()
|> Tuple.to_list()
|> List.first() || @burn_address
conn
|> put_status(200)
|> json(Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string, params["from"]))
end
end
def methods_write_proxy(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)} do
implementation_address_hash_string =
address.smart_contract
|> SmartContract.get_implementation_address_hash()
|> Tuple.to_list()
|> List.first() || @burn_address
conn
|> put_status(200)
|> json(Writer.write_functions_proxy(implementation_address_hash_string))
end
end
def query_read_method(
conn,
%{"address_hash" => address_hash_string, "is_custom_abi" => custom_abi, "contract_type" => type, "args" => args} =
params
) do
custom_abi =
if parse_boolean(params["is_custom_abi"]), do: AddressView.fetch_custom_abi(conn, params["id"]), else: nil
contract_type = if type == "proxy", do: :proxy, else: :regular
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, _address}} <- {:not_found, Chain.find_contract_address(address_hash, [])} do
%{output: outputs, names: _names} =
if custom_abi do
Reader.query_function_with_names_custom_abi(
address_hash,
%{method_id: params["method_id"], args: args},
params["from"],
custom_abi.abi
)
else
Reader.query_function_with_names(
address_hash,
%{method_id: params["method_id"], args: args},
contract_type,
params["from"]
)
end
conn
|> put_status(200)
|> json(outputs)
end
end
end

@ -42,9 +42,9 @@ defmodule BlockScoutWeb.SmartContractController do
end
else
if contract_type == "proxy" do
Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string)
Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string, params["from"])
else
Reader.read_only_functions(address_hash)
Reader.read_only_functions(address_hash, params["from"])
end
end
@ -101,7 +101,7 @@ defmodule BlockScoutWeb.SmartContractController do
def index(conn, _), do: not_found(conn)
defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do
defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do
with custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string),
false <- is_nil(custom_abi),
abi <- custom_abi.abi,
@ -110,7 +110,7 @@ defmodule BlockScoutWeb.SmartContractController do
if action == "write" do
Writer.filter_write_functions(abi)
else
Reader.read_only_functions_from_abi(abi, address_hash)
Reader.read_only_functions_from_abi_with_sender(abi, address_hash, params["from"])
end
read_functions_required_wallet =

@ -83,11 +83,11 @@ defmodule BlockScoutWeb.AddressContractView do
end
end
defp decode_data("0x" <> encoded_data, types) do
def decode_data("0x" <> encoded_data, types) do
decode_data(encoded_data, types)
end
defp decode_data(encoded_data, types) do
def decode_data(encoded_data, types) do
encoded_data
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.API.V2.AddressView do
use BlockScoutWeb, :view
alias BlockScoutWeb.AddressView
alias ABI.FunctionSelector
alias BlockScoutWeb.{ABIEncodedValueView, AddressContractView, AddressView}
alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView}
alias BlockScoutWeb.API.V2.Helper
alias Explorer.{Chain, Market}
@ -32,6 +33,10 @@ defmodule BlockScoutWeb.API.V2.AddressView do
Enum.map(coin_balances_by_day, &prepare_coin_balance_history_by_day_entry/1)
end
def render("smart_contract.json", %{address: address}) do
prepare_smart_contract(address)
end
def prepare_address(address, conn \\ nil) do
base_info = Helper.address_with_info(conn, address, address.hash)
is_proxy = AddressView.smart_contract_is_proxy?(address)
@ -55,6 +60,9 @@ defmodule BlockScoutWeb.API.V2.AddressView do
creation_tx = creator_hash && AddressView.transaction_hash(address)
token = address.token && TokenView.render("token.json", %{token: Market.add_price(address.token)})
write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash)
read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash)
Map.merge(base_info, %{
"creator_address_hash" => creator_hash && Address.checksum(creator_hash),
"creation_tx_hash" => creation_tx,
@ -63,7 +71,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do
"exchange_rate" => exchange_rate,
"implementation_name" => implementation_name,
"implementation_address" => implementation_address,
"block_number_balance_updated_at" => address.fetched_coin_balance_block_number
"block_number_balance_updated_at" => address.fetched_coin_balance_block_number,
"has_custom_methods_read" => read_custom_abi?,
"has_custom_methods_write" => write_custom_abi?,
"has_methods_read" => AddressView.smart_contract_with_read_only_functions?(address) || read_custom_abi?,
"has_methods_write" => AddressView.smart_contract_with_write_functions?(address) || write_custom_abi?,
"has_methods_read_proxy" => is_proxy,
"has_methods_write_proxy" => AddressView.smart_contract_with_write_functions?(address) && is_proxy
})
end
@ -91,4 +105,100 @@ defmodule BlockScoutWeb.API.V2.AddressView do
"value" => coin_balance_by_day.value
}
end
def prepare_smart_contract(address) do
minimal_proxy_template = Chain.get_minimal_proxy_template(address.hash)
metadata_for_verification =
minimal_proxy_template || Chain.get_address_verified_twin_contract(address.hash).verified_contract
smart_contract_verified = AddressView.smart_contract_verified?(address)
additional_sources_from_twin = Chain.get_address_verified_twin_contract(address.hash).additional_sources
fully_verified = Chain.smart_contract_fully_verified?(address.hash)
additional_sources =
if smart_contract_verified, do: address.smart_contract_additional_sources, else: additional_sources_from_twin
visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?()
target_contract = if smart_contract_verified, do: address.smart_contract, else: metadata_for_verification
%{
"verified_twin_address_hash" => metadata_for_verification && metadata_for_verification.address_hash,
"is_verified" => smart_contract_verified,
"is_changed_bytecode" => smart_contract_verified && address.smart_contract.is_changed_bytecode,
"is_partially_verified" => address.smart_contract.partially_verified && smart_contract_verified,
"is_fully_verified" => fully_verified,
"is_verified_via_sourcify" => address.smart_contract.verified_via_sourcify && smart_contract_verified,
"is_vyper_contract" => target_contract.is_vyper_contract,
"minimal_proxy_address_hash" =>
minimal_proxy_template && Address.checksum(metadata_for_verification.address_hash),
"sourcify_repo_url" =>
if(address.smart_contract.verified_via_sourcify && smart_contract_verified,
do: AddressContractView.sourcify_repo_url(address.hash, address.smart_contract.partially_verified)
),
"can_be_visualized_via_sol2uml" =>
visualize_sol2uml_enabled && !target_contract.is_vyper_contract && !is_nil(target_contract.abi),
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract.compiler_version,
"optimization_enabled" => if(target_contract.is_vyper_contract, do: nil, else: target_contract.optimization),
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at,
"abi" => target_contract.abi,
"source_code" => target_contract.contract_source_code,
"file_path" => target_contract.file_path,
"additional_sources" => Enum.map(additional_sources, &prepare_additional_sourse/1),
"compiler_settings" => target_contract.compiler_settings,
"external_libraries" => target_contract.external_libraries,
"constructor_args" => target_contract.constructor_arguments,
"decoded_constructor_args" =>
format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments)
}
|> Map.merge(bytecode_info(address))
# |>
end
defp bytecode_info(address) do
case AddressContractView.contract_creation_code(address) do
{:selfdestructed, init} ->
%{
"is_self_destructed" => true,
"deployed_bytecode" => nil,
"creation_bytecode" => init
}
{:ok, contract_code} ->
%{
"is_self_destructed" => false,
"deployed_bytecode" => contract_code,
"creation_bytecode" => AddressContractView.creation_code(address)
}
end
end
defp prepare_additional_sourse(source) do
%{
"source_code" => source.contract_source_code,
"file_path" => source.file_name
}
end
def format_constructor_arguments(abi, constructor_arguments) do
constructor_abi = Enum.find(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end)
input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1)
result =
constructor_arguments
|> AddressContractView.decode_data(input_types)
|> Enum.zip(constructor_abi["inputs"])
|> Enum.map(fn {value, %{"type" => type} = input_arg} ->
{ABIEncodedValueView.value_json(type, value), input_arg}
end)
result
rescue
_ -> nil
end
end

@ -81,6 +81,7 @@ defmodule BlockScoutWeb.API.V2.Helper do
def is_smart_contract(_), do: false
def is_verified(%Address{smart_contract: nil}), do: false
def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false
def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil
def is_verified(%Address{smart_contract: _}), do: true

@ -217,7 +217,7 @@ defmodule Explorer.SmartContract.Reader do
]
"""
@spec read_only_functions(Hash.t()) :: [%{}]
def read_only_functions(contract_address_hash) do
def read_only_functions(contract_address_hash, from \\ nil) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
@ -228,11 +228,11 @@ defmodule Explorer.SmartContract.Reader do
[]
_ ->
read_only_functions_from_abi(abi, contract_address_hash)
read_only_functions_from_abi_with_sender(abi, contract_address_hash, from)
end
end
def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string) do
def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string, from \\ nil) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
case implementation_abi do
@ -240,7 +240,7 @@ defmodule Explorer.SmartContract.Reader do
[]
_ ->
read_only_functions_from_abi(implementation_abi, contract_address_hash)
read_only_functions_from_abi_with_sender(implementation_abi, contract_address_hash, from)
end
end
@ -279,15 +279,15 @@ defmodule Explorer.SmartContract.Reader do
end
end
def read_only_functions_from_abi([_ | _] = abi, contract_address_hash) do
def read_only_functions_from_abi_with_sender([_ | _] = abi, contract_address_hash, from) do
abi_with_method_id = get_abi_with_method_id(abi)
abi_with_method_id
|> Enum.filter(&Helper.queriable_method?(&1))
|> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false))
|> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, from))
end
def read_only_functions_from_abi(_, _), do: []
def read_only_functions_from_abi_with_sender(_, _, _), do: []
def read_functions_required_wallet_from_abi([_ | _] = abi) do
abi_with_method_id = get_abi_with_method_id(abi)
@ -346,7 +346,7 @@ defmodule Explorer.SmartContract.Reader do
"tuple[#{tuple_types}]"
end
def fetch_current_value_from_blockchain(function, abi, contract_address_hash, leave_error_as_map) do
def fetch_current_value_from_blockchain(function, abi, contract_address_hash, leave_error_as_map, from \\ nil) do
values =
case function do
%{"inputs" => []} ->
@ -355,7 +355,7 @@ defmodule Explorer.SmartContract.Reader do
outputs = function["outputs"]
contract_address_hash
|> query_verified_contract(%{method_id => normalize_args(args)}, leave_error_as_map, abi)
|> query_verified_contract(%{method_id => normalize_args(args)}, from, leave_error_as_map, abi)
|> link_outputs_and_values(outputs, method_id)
_ ->

Loading…
Cancel
Save