-
<%= gettext "Raw Trace" %>
+
<%= gettext "Raw Trace" %>
+
+
+
+
+
+
<%= if Enum.count(@internal_transactions) > 0 do %>
<%= for {line, number} <- raw_traces_with_lines(@internal_transactions) do %><%= line %>
<% end %>
<% else %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
index 58cdff8db8..1930c4b8d7 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
@@ -1,7 +1,13 @@
defmodule BlockScoutWeb.AddressReadContractView do
use BlockScoutWeb, :view
- def queryable?(inputs), do: Enum.any?(inputs)
+ def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
+
+ def queryable?(inputs) when is_nil(inputs), do: false
+
+ def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
+
+ def outputs?(outputs) when is_nil(outputs), do: false
def address?(type), do: type == "address"
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
new file mode 100644
index 0000000000..178e900fe0
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
@@ -0,0 +1,13 @@
+defmodule BlockScoutWeb.AddressReadProxyView do
+ use BlockScoutWeb, :view
+
+ def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
+
+ def queryable?(inputs) when is_nil(inputs), do: false
+
+ def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
+
+ def outputs?(outputs) when is_nil(outputs), do: false
+
+ def address?(type), do: type == "address"
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
index 1b14c7b424..3724c248fe 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
@@ -8,6 +8,7 @@ defmodule BlockScoutWeb.AddressView do
alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei}
alias Explorer.Chain.Block.Reward
alias Explorer.ExchangeRates.Token, as: TokenExchangeRate
+ alias Explorer.SmartContract.Writer
@dialyzer :no_match
@@ -18,6 +19,9 @@ defmodule BlockScoutWeb.AddressView do
"internal_transactions",
"token_transfers",
"read_contract",
+ "read_proxy",
+ "write_contract",
+ "write_proxy",
"tokens",
"transactions",
"validations"
@@ -228,6 +232,21 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
+ def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
+ Chain.is_proxy_contract?(address.smart_contract.abi)
+ end
+
+ def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false
+
+ def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do
+ Enum.any?(
+ address.smart_contract.abi,
+ &Writer.write_function?(&1)
+ )
+ end
+
+ def smart_contract_with_write_functions?(%Address{smart_contract: nil}), do: false
+
def has_decompiled_code?(address) do
address.has_decompiled_code? ||
(Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0)
@@ -326,6 +345,9 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["contracts"]), do: gettext("Code")
defp tab_name(["decompiled_contracts"]), do: gettext("Decompiled Code")
defp tab_name(["read_contract"]), do: gettext("Read Contract")
+ defp tab_name(["read_proxy"]), do: gettext("Read Proxy")
+ defp tab_name(["write_contract"]), do: gettext("Write Contract")
+ defp tab_name(["write_proxy"]), do: gettext("Write Proxy")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated")
defp tab_name(["logs"]), do: gettext("Logs")
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
new file mode 100644
index 0000000000..37b82f72cc
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
@@ -0,0 +1,13 @@
+defmodule BlockScoutWeb.AddressWriteContractView do
+ use BlockScoutWeb, :view
+
+ def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
+
+ def queryable?(inputs) when is_nil(inputs), do: false
+
+ def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
+
+ def outputs?(outputs) when is_nil(outputs), do: false
+
+ def address?(type), do: type == "address"
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
new file mode 100644
index 0000000000..9825477c3d
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
@@ -0,0 +1,13 @@
+defmodule BlockScoutWeb.AddressWriteProxyView do
+ use BlockScoutWeb, :view
+
+ def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
+
+ def queryable?(inputs) when is_nil(inputs), do: false
+
+ def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
+
+ def outputs?(outputs) when is_nil(outputs), do: false
+
+ def address?(type), do: type == "address"
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
index 62da759428..30f9803259 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
@@ -22,6 +22,11 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data)
end
+ def render("pendingtxlist.json", %{transactions: transactions}) do
+ data = Enum.map(transactions, &prepare_pending_transaction/1)
+ RPCView.render("show.json", data: data)
+ end
+
def render("txlist.json", %{transactions: transactions}) do
data = Enum.map(transactions, &prepare_transaction/1)
RPCView.render("show.json", data: data)
@@ -79,6 +84,22 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
}
end
+ defp prepare_pending_transaction(transaction) do
+ %{
+ "hash" => "#{transaction.hash}",
+ "nonce" => "#{transaction.nonce}",
+ "from" => "#{transaction.from_address_hash}",
+ "to" => "#{transaction.to_address_hash}",
+ "value" => "#{transaction.value.value}",
+ "gas" => "#{transaction.gas}",
+ "gasPrice" => "#{transaction.gas_price.value}",
+ "input" => "#{transaction.input}",
+ "contractAddress" => "#{transaction.created_contract_address_hash}",
+ "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
+ "gasUsed" => "#{transaction.gas_used}"
+ }
+ end
+
defp prepare_transaction(transaction) do
%{
"blockNumber" => "#{transaction.block_number}",
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
index b9e8f7430d..4b81b11091 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
@@ -70,6 +70,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
"gasUsed" => "#{transaction.gas_used}",
"gasPrice" => "#{transaction.gas_price.value}",
"logs" => Enum.map(logs, &prepare_log/1),
+ "revertReason" => "#{transaction.revert_reason}",
"next_page_params" => next_page_params
}
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
index 051ec77024..ab3e02818b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
@@ -17,12 +17,7 @@ defmodule BlockScoutWeb.LayoutView do
},
%{
title: "xDai Chain",
- url: "https://blockscout.com/poa/dai"
- },
- %{
- title: "Kovan Testnet",
- url: "https://blockscout.com/eth/kovan",
- test_net?: true
+ url: "https://blockscout.com/poa/xdai"
},
%{
title: "Ethereum Classic",
@@ -221,6 +216,21 @@ defmodule BlockScoutWeb.LayoutView do
end
end
+ def external_apps_list do
+ if Application.get_env(:block_scout_web, :external_apps) do
+ try do
+ :block_scout_web
+ |> Application.get_env(:external_apps)
+ |> Parser.parse!(%{keys: :atoms!})
+ rescue
+ _ ->
+ []
+ end
+ else
+ []
+ end
+ end
+
defp validate_url(url) when is_binary(url) do
case URI.parse(url) do
%URI{host: nil} -> :error
diff --git a/apps/block_scout_web/lib/block_scout_web/views/log_view.ex b/apps/block_scout_web/lib/block_scout_web/views/log_view.ex
new file mode 100644
index 0000000000..fe12ba9cce
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/log_view.ex
@@ -0,0 +1,3 @@
+defmodule BlockScoutWeb.LogView do
+ use BlockScoutWeb, :view
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
index fef4fbc9d2..f0449424fb 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
@@ -1,7 +1,35 @@
defmodule BlockScoutWeb.SmartContractView do
use BlockScoutWeb, :view
- def queryable?(inputs), do: Enum.any?(inputs)
+ def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
+
+ def queryable?(inputs) when is_nil(inputs), do: false
+
+ def writeable?(function) when not is_nil(function),
+ do:
+ !constructor?(function) && !event?(function) &&
+ (payable?(function) || nonpayable?(function))
+
+ def writeable?(function) when is_nil(function), do: false
+
+ def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
+
+ def outputs?(outputs) when is_nil(outputs), do: false
+
+ defp event?(function), do: function["type"] == "event"
+
+ defp constructor?(function), do: function["type"] == "constructor"
+
+ def payable?(function), do: function["stateMutability"] == "payable" || function["payable"]
+
+ def nonpayable?(function) do
+ if function["type"] do
+ function["stateMutability"] == "nonpayable" ||
+ (!function["payable"] && !function["constant"] && !function["stateMutability"])
+ else
+ false
+ end
+ end
def address?(type), do: type in ["address", "address payable"]
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
index 1c01ab80a3..c128083916 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
@@ -237,6 +237,10 @@ defmodule BlockScoutWeb.TransactionView do
Chain.transaction_to_status(transaction)
end
+ def transaction_revert_reason(transaction) do
+ Chain.transaction_to_revert_reason(transaction)
+ end
+
def empty_exchange_rate?(exchange_rate) do
Token.null?(exchange_rate)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex
index 38abb8add5..fd0fc0e444 100644
--- a/apps/block_scout_web/lib/block_scout_web/web_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex
@@ -125,6 +125,27 @@ defmodule BlockScoutWeb.WebRouter do
as: :read_contract
)
+ resources(
+ "/read_proxy",
+ AddressReadProxyController,
+ only: [:index, :show],
+ as: :read_proxy
+ )
+
+ resources(
+ "/write_contract",
+ AddressWriteContractController,
+ only: [:index, :show],
+ as: :write_contract
+ )
+
+ resources(
+ "/write_proxy",
+ AddressWriteProxyController,
+ only: [:index, :show],
+ as: :write_proxy
+ )
+
resources(
"/token_transfers",
AddressTokenTransferController,
diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs
index f3d32c46fc..b725b274c6 100644
--- a/apps/block_scout_web/mix.exs
+++ b/apps/block_scout_web/mix.exs
@@ -60,13 +60,13 @@ defmodule BlockScoutWeb.Mixfile do
defp deps do
[
# GraphQL toolkit
- {:absinthe, "~> 1.5.0"},
+ {:absinthe, "~> 1.5.2"},
# Integrates Absinthe subscriptions with Phoenix
{:absinthe_phoenix, "~> 2.0.0"},
# Plug support for Absinthe
{:absinthe_plug, "~> 1.5.0"},
# Absinthe support for the Relay framework
- {:absinthe_relay, "~> 1.5.0"},
+ {:absinthe_relay, "~> 1.5"},
{:bypass, "~> 1.0", only: :test},
# To add (CORS)(https://www.w3.org/TR/cors/)
{:cors_plug, "~> 2.0"},
@@ -99,8 +99,8 @@ defmodule BlockScoutWeb.Mixfile do
{:logger_file_backend, "~> 0.0.10"},
{:math, "~> 0.3.0"},
{:mock, "~> 0.3.0", only: [:test], runtime: false},
- {:phoenix, "== 1.5.3"},
- {:phoenix_ecto, "~> 4.0"},
+ {:phoenix, "== 1.5.4"},
+ {:phoenix_ecto, "~> 4.1"},
{:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.2", only: [:dev]},
{:phoenix_pubsub, "~> 2.0"},
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index b4edc9e73c..12ef88e1a0 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:196
+#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:247
+#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -124,7 +124,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
-#: lib/block_scout_web/views/address_view.ex:100
+#: lib/block_scout_web/views/address_view.ex:104
msgid "Address"
msgstr ""
@@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:72
+#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
@@ -338,14 +338,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
-#: lib/block_scout_web/views/address_view.ex:98
+#: lib/block_scout_web/views/address_view.ex:102
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
-#: lib/block_scout_web/views/address_view.ex:38
-#: lib/block_scout_web/views/address_view.ex:72
+#: lib/block_scout_web/views/address_view.ex:42
+#: lib/block_scout_web/views/address_view.ex:76
msgid "Contract Address Pending"
msgstr ""
@@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:336
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:333
+#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@@ -448,11 +448,8 @@ msgid "Difficulty"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:66
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:119
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:69
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:122
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
msgid "Copy Value"
msgstr ""
@@ -478,23 +475,20 @@ msgid "Curl"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:56
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:109
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:175
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:112
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:179
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:100
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:103
msgid "Data"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:37
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:90
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:52
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:93
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55
msgid "Decoded"
msgstr ""
@@ -568,7 +562,8 @@ msgid "ERC-721 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:50
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:78
msgid "ETH"
msgstr ""
@@ -604,7 +599,7 @@ msgid "Enter the Solidity Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:30
msgid "Error rendering value"
msgstr ""
@@ -621,12 +616,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:251
+#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:249
+#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -636,7 +631,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -646,8 +641,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@@ -666,7 +661,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
-#: lib/block_scout_web/templates/transaction/overview.html.eex:79
+#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@@ -737,7 +732,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -753,8 +748,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:115
-#: lib/block_scout_web/templates/transaction/overview.html.eex:119
+#: lib/block_scout_web/templates/transaction/overview.html.eex:123
+#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@@ -776,7 +771,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:248
+#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@@ -789,7 +784,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
-#: lib/block_scout_web/templates/transaction/overview.html.eex:84
+#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@@ -891,7 +886,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/views/transaction_view.ex:329
+#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@@ -903,10 +898,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
-#: lib/block_scout_web/views/address_view.ex:325
+#: lib/block_scout_web/views/address_view.ex:344
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
-#: lib/block_scout_web/views/transaction_view.ex:390
+#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@@ -922,7 +917,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
-#: lib/block_scout_web/views/transaction_view.ex:339
+#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@@ -932,10 +927,7 @@ msgid "If you have just submitted this transaction please wait for at least 30 s
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:55
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:108
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:111
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6
msgid "Indexed?"
msgstr ""
@@ -1005,12 +997,15 @@ msgid "License ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:283
+#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
+#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
+#: lib/block_scout_web/templates/address_write_contract/index.html.eex:14
+#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..."
msgstr ""
@@ -1021,10 +1016,7 @@ msgid "Loading...."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:103
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:106
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2
msgid "Log Data"
msgstr ""
@@ -1041,7 +1033,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
-#: lib/block_scout_web/views/address_view.ex:138
+#: lib/block_scout_web/views/address_view.ex:142
msgid "Market Cap"
msgstr ""
@@ -1085,13 +1077,10 @@ msgid "Must be set to:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:106
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:109
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
msgid "Name"
msgstr ""
@@ -1158,9 +1147,9 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
-#: lib/block_scout_web/views/transaction_view.ex:246
-#: lib/block_scout_web/views/transaction_view.ex:280
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
+#: lib/block_scout_web/views/transaction_view.ex:250
+#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@@ -1191,24 +1180,24 @@ msgid "QR Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:22
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:109
+#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:393
+#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@@ -1254,14 +1243,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:164
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:181
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@@ -1451,8 +1440,8 @@ msgid "Topic"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:145
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:149
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:70
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73
msgid "Topics"
msgstr ""
@@ -1488,7 +1477,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:101
+#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@@ -1509,16 +1498,13 @@ msgid "Twitter"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:54
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
msgid "Type"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:122
+#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@@ -1544,7 +1530,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:277
+#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@@ -1574,8 +1560,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@@ -1645,7 +1631,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:49
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77
msgid "WEI"
msgstr ""
@@ -1752,7 +1738,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:273
+#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@@ -1772,7 +1758,7 @@ msgid "Loading chart"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:189
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
msgid "Log Index"
msgstr ""
@@ -1799,15 +1785,15 @@ msgid "Transfers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:134
-#: lib/block_scout_web/templates/transaction/overview.html.eex:147
+#: lib/block_scout_web/templates/transaction/overview.html.eex:142
+#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
-#: lib/block_scout_web/views/address_view.ex:330
+#: lib/block_scout_web/views/address_view.ex:352
msgid "Blocks Validated"
msgstr ""
@@ -1817,18 +1803,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
-#: lib/block_scout_web/views/address_view.ex:326
+#: lib/block_scout_web/views/address_view.ex:345
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
-#: lib/block_scout_web/views/address_view.ex:329
+#: lib/block_scout_web/views/address_view.ex:351
msgid "Coin Balance History"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_view.ex:327
+#: lib/block_scout_web/views/address_view.ex:346
msgid "Decompiled Code"
msgstr ""
@@ -1837,8 +1823,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
-#: lib/block_scout_web/views/address_view.ex:324
-#: lib/block_scout_web/views/transaction_view.ex:391
+#: lib/block_scout_web/views/address_view.ex:343
+#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@@ -1847,15 +1833,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
-#: lib/block_scout_web/views/address_view.ex:331
-#: lib/block_scout_web/views/transaction_view.ex:392
+#: lib/block_scout_web/views/address_view.ex:353
+#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
-#: lib/block_scout_web/views/address_view.ex:328
+#: lib/block_scout_web/views/address_view.ex:347
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@@ -1864,7 +1850,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
-#: lib/block_scout_web/views/address_view.ex:322
+#: lib/block_scout_web/views/address_view.ex:341
msgid "Tokens"
msgstr ""
@@ -1876,31 +1862,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
-#: lib/block_scout_web/views/address_view.ex:323
+#: lib/block_scout_web/views/address_view.ex:342
msgid "Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:232
+#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:328
+#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:214
+#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:327
+#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@@ -1908,3 +1894,47 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+msgid "Revert reason"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:69
+#: lib/block_scout_web/views/address_view.ex:348
+msgid "Read Proxy"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:76
+#: lib/block_scout_web/views/address_view.ex:349
+msgid "Write Contract"
+msgstr ""
+
+#, elixir-format
+#:
+#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:12
+msgid "Waiting for transaction's confirmation..."
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
+msgid "Write"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:83
+#: lib/block_scout_web/views/address_view.ex:350
+msgid "Write Proxy"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
+msgid "Apps"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15
+msgid "Copy Raw Trace"
+msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index b4edc9e73c..12ef88e1a0 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:196
+#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:247
+#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -124,7 +124,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
-#: lib/block_scout_web/views/address_view.ex:100
+#: lib/block_scout_web/views/address_view.ex:104
msgid "Address"
msgstr ""
@@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:72
+#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
@@ -338,14 +338,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
-#: lib/block_scout_web/views/address_view.ex:98
+#: lib/block_scout_web/views/address_view.ex:102
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
-#: lib/block_scout_web/views/address_view.ex:38
-#: lib/block_scout_web/views/address_view.ex:72
+#: lib/block_scout_web/views/address_view.ex:42
+#: lib/block_scout_web/views/address_view.ex:76
msgid "Contract Address Pending"
msgstr ""
@@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:336
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:333
+#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@@ -448,11 +448,8 @@ msgid "Difficulty"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:66
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:119
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:69
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:122
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
msgid "Copy Value"
msgstr ""
@@ -478,23 +475,20 @@ msgid "Curl"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:56
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:109
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:175
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:112
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:179
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:100
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:103
msgid "Data"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:31
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:37
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:90
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:52
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:32
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:40
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:93
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:55
msgid "Decoded"
msgstr ""
@@ -568,7 +562,8 @@ msgid "ERC-721 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:50
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:78
msgid "ETH"
msgstr ""
@@ -604,7 +599,7 @@ msgid "Enter the Solidity Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:30
msgid "Error rendering value"
msgstr ""
@@ -621,12 +616,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:251
+#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:249
+#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -636,7 +631,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -646,8 +641,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@@ -666,7 +661,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
-#: lib/block_scout_web/templates/transaction/overview.html.eex:79
+#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@@ -737,7 +732,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -753,8 +748,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:115
-#: lib/block_scout_web/templates/transaction/overview.html.eex:119
+#: lib/block_scout_web/templates/transaction/overview.html.eex:123
+#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@@ -776,7 +771,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:248
+#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@@ -789,7 +784,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
-#: lib/block_scout_web/templates/transaction/overview.html.eex:84
+#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@@ -891,7 +886,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/views/transaction_view.ex:329
+#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@@ -903,10 +898,10 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
-#: lib/block_scout_web/views/address_view.ex:325
+#: lib/block_scout_web/views/address_view.ex:344
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
-#: lib/block_scout_web/views/transaction_view.ex:390
+#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@@ -922,7 +917,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
-#: lib/block_scout_web/views/transaction_view.ex:339
+#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@@ -932,10 +927,7 @@ msgid "If you have just submitted this transaction please wait for at least 30 s
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:55
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:108
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:111
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6
msgid "Indexed?"
msgstr ""
@@ -1005,12 +997,15 @@ msgid "License ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:283
+#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
+#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
+#: lib/block_scout_web/templates/address_write_contract/index.html.eex:14
+#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..."
msgstr ""
@@ -1021,10 +1016,7 @@ msgid "Loading...."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:50
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:103
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:53
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:106
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2
msgid "Log Data"
msgstr ""
@@ -1041,7 +1033,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
-#: lib/block_scout_web/views/address_view.ex:138
+#: lib/block_scout_web/views/address_view.ex:142
msgid "Market Cap"
msgstr ""
@@ -1085,13 +1077,10 @@ msgid "Must be set to:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:106
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:109
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
msgid "Name"
msgstr ""
@@ -1158,9 +1147,9 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
-#: lib/block_scout_web/views/transaction_view.ex:246
-#: lib/block_scout_web/views/transaction_view.ex:280
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
+#: lib/block_scout_web/views/transaction_view.ex:250
+#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@@ -1191,24 +1180,24 @@ msgid "QR Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:22
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:109
+#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:393
+#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@@ -1254,14 +1243,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:164
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:181
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@@ -1451,8 +1440,8 @@ msgid "Topic"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:145
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:149
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:70
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73
msgid "Topics"
msgstr ""
@@ -1488,7 +1477,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:101
+#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@@ -1509,16 +1498,13 @@ msgid "Twitter"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:54
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:107
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:110
+#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
msgid "Type"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:122
+#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@@ -1544,7 +1530,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:277
+#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@@ -1574,8 +1560,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@@ -1645,7 +1631,7 @@ msgid "View transaction %{transaction} on %{subnetwork}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:49
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77
msgid "WEI"
msgstr ""
@@ -1752,7 +1738,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:273
+#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@@ -1772,7 +1758,7 @@ msgid "Loading chart"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:189
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
msgid "Log Index"
msgstr ""
@@ -1799,15 +1785,15 @@ msgid "Transfers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:134
-#: lib/block_scout_web/templates/transaction/overview.html.eex:147
+#: lib/block_scout_web/templates/transaction/overview.html.eex:142
+#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
-#: lib/block_scout_web/views/address_view.ex:330
+#: lib/block_scout_web/views/address_view.ex:352
msgid "Blocks Validated"
msgstr ""
@@ -1817,18 +1803,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
-#: lib/block_scout_web/views/address_view.ex:326
+#: lib/block_scout_web/views/address_view.ex:345
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
-#: lib/block_scout_web/views/address_view.ex:329
+#: lib/block_scout_web/views/address_view.ex:351
msgid "Coin Balance History"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_view.ex:327
+#: lib/block_scout_web/views/address_view.ex:346
msgid "Decompiled Code"
msgstr ""
@@ -1837,8 +1823,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
-#: lib/block_scout_web/views/address_view.ex:324
-#: lib/block_scout_web/views/transaction_view.ex:391
+#: lib/block_scout_web/views/address_view.ex:343
+#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@@ -1847,15 +1833,15 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
-#: lib/block_scout_web/views/address_view.ex:331
-#: lib/block_scout_web/views/transaction_view.ex:392
+#: lib/block_scout_web/views/address_view.ex:353
+#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
-#: lib/block_scout_web/views/address_view.ex:328
+#: lib/block_scout_web/views/address_view.ex:347
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@@ -1864,7 +1850,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
-#: lib/block_scout_web/views/address_view.ex:322
+#: lib/block_scout_web/views/address_view.ex:341
msgid "Tokens"
msgstr ""
@@ -1876,31 +1862,31 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
-#: lib/block_scout_web/views/address_view.ex:323
+#: lib/block_scout_web/views/address_view.ex:342
msgid "Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:232
+#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:328
+#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:214
+#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:327
+#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@@ -1908,3 +1894,47 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+msgid "Revert reason"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:69
+#: lib/block_scout_web/views/address_view.ex:348
+msgid "Read Proxy"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:76
+#: lib/block_scout_web/views/address_view.ex:349
+msgid "Write Contract"
+msgstr ""
+
+#, elixir-format
+#:
+#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:12
+msgid "Waiting for transaction's confirmation..."
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:47
+msgid "Write"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address/_tabs.html.eex:83
+#: lib/block_scout_web/views/address_view.ex:350
+msgid "Write Proxy"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
+msgid "Apps"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15
+msgid "Copy Raw Trace"
+msgstr ""
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
new file mode 100644
index 0000000000..3c37905cf9
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
@@ -0,0 +1,79 @@
+defmodule BlockScoutWeb.AddressReadProxyControllerTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.Address
+
+ import Mox
+
+ describe "GET index/3" do
+ setup :set_mox_global
+
+ setup do
+ configuration = Application.get_env(:explorer, :checksum_function)
+ Application.put_env(:explorer, :checksum_function, :eth)
+
+ :ok
+
+ on_exit(fn ->
+ Application.put_env(:explorer, :checksum_function, configuration)
+ end)
+ end
+
+ test "with invalid address hash", %{conn: conn} do
+ conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, "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_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(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) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ insert(:smart_contract, address_hash: contract_address.hash)
+
+ conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 200)
+ assert contract_address.hash == conn.assigns.address.hash
+ assert %Token{} = conn.assigns.exchange_rate
+ end
+
+ test "returns not found for an unverified contract", %{conn: conn} do
+ contract_address = insert(:contract_address)
+
+ transaction = insert(:transaction, from_address: contract_address) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 404)
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
new file mode 100644
index 0000000000..9356c474ea
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
@@ -0,0 +1,81 @@
+defmodule BlockScoutWeb.AddressWriteContractControllerTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.Address
+
+ import Mox
+
+ describe "GET index/3" do
+ setup :set_mox_global
+
+ setup do
+ configuration = Application.get_env(:explorer, :checksum_function)
+ Application.put_env(:explorer, :checksum_function, :eth)
+
+ :ok
+
+ on_exit(fn ->
+ Application.put_env(:explorer, :checksum_function, configuration)
+ end)
+ end
+
+ test "with invalid address hash", %{conn: conn} do
+ conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, "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_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(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) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ insert(:smart_contract, address_hash: contract_address.hash)
+
+ conn =
+ get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 200)
+ assert contract_address.hash == conn.assigns.address.hash
+ assert %Token{} = conn.assigns.exchange_rate
+ end
+
+ test "returns not found for an unverified contract", %{conn: conn} do
+ contract_address = insert(:contract_address)
+
+ transaction = insert(:transaction, from_address: contract_address) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ conn =
+ get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 404)
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
new file mode 100644
index 0000000000..d4fc4917aa
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
@@ -0,0 +1,81 @@
+defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias Explorer.ExchangeRates.Token
+ alias Explorer.Chain.Address
+
+ import Mox
+
+ describe "GET index/3" do
+ setup :set_mox_global
+
+ setup do
+ configuration = Application.get_env(:explorer, :checksum_function)
+ Application.put_env(:explorer, :checksum_function, :eth)
+
+ :ok
+
+ on_exit(fn ->
+ Application.put_env(:explorer, :checksum_function, configuration)
+ end)
+ end
+
+ test "with invalid address hash", %{conn: conn} do
+ conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, "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_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(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) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ insert(:smart_contract, address_hash: contract_address.hash)
+
+ conn =
+ get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 200)
+ assert contract_address.hash == conn.assigns.address.hash
+ assert %Token{} = conn.assigns.exchange_rate
+ end
+
+ test "returns not found for an unverified contract", %{conn: conn} do
+ contract_address = insert(:contract_address)
+
+ transaction = insert(:transaction, from_address: contract_address) |> with_block()
+
+ insert(
+ :internal_transaction_create,
+ index: 0,
+ transaction: transaction,
+ created_contract_address: contract_address,
+ block_hash: transaction.block_hash,
+ block_index: 0
+ )
+
+ conn =
+ get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
+
+ assert html_response(conn, 404)
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
index 5378ca6cfe..32d2be6be4 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
@@ -1419,6 +1419,332 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
end
+ describe "pendingtxlist" do
+ test "with missing address hash", %{conn: conn} do
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["message"] =~ "'address' is required"
+ assert response["status"] == "0"
+ assert Map.has_key?(response, "result")
+ refute response["result"]
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with an invalid address hash", %{conn: conn} do
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "badhash"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["message"] =~ "Invalid address format"
+ assert response["status"] == "0"
+ assert Map.has_key?(response, "result")
+ refute response["result"]
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with an address that doesn't exist", %{conn: conn} do
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"] == []
+ assert response["status"] == "0"
+ assert response["message"] == "No transactions found"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with a valid address", %{conn: conn} do
+ address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}"
+ }
+
+ expected_result = [
+ %{
+ "hash" => "#{transaction.hash}",
+ "nonce" => "#{transaction.nonce}",
+ "from" => "#{transaction.from_address_hash}",
+ "to" => "#{transaction.to_address_hash}",
+ "value" => "#{transaction.value.value}",
+ "gas" => "#{transaction.gas}",
+ "gasPrice" => "#{transaction.gas_price.value}",
+ "input" => "#{transaction.input}",
+ "contractAddress" => "#{transaction.created_contract_address_hash}",
+ "cumulativeGasUsed" => "#{transaction.cumulative_gas_used}",
+ "gasUsed" => "#{transaction.gas_used}"
+ }
+ ]
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"] == expected_result
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with address with multiple transactions", %{conn: conn} do
+ address1 = insert(:address)
+ address2 = insert(:address)
+
+ transactions =
+ 3
+ |> insert_list(:transaction, from_address: address1)
+
+ :transaction
+ |> insert(from_address: address2)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address1.hash}"
+ }
+
+ expected_transaction_hashes = Enum.map(transactions, &"#{&1.hash}")
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert length(response["result"]) == 3
+
+ for returned_transaction <- response["result"] do
+ assert returned_transaction["hash"] in expected_transaction_hashes
+ end
+
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with valid pagination params", %{conn: conn} do
+ address = insert(:address)
+
+ _transactions_1 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ _transactions_2 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ transactions_3 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}",
+ # page number
+ "page" => "1",
+ # page size
+ "offset" => "2"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ page1_hashes = Enum.map(response["result"], & &1["hash"])
+
+ assert length(response["result"]) == 2
+
+ for transaction <- transactions_3 do
+ assert "#{transaction.hash}" in page1_hashes
+ end
+
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "ignores pagination params when invalid", %{conn: conn} do
+ address = insert(:address)
+
+ _transactions_1 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ _transactions_2 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ _transactions_3 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}",
+ # page number
+ "page" => "invalidpage",
+ # page size
+ "offset" => "invalidoffset"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert length(response["result"]) == 6
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "ignores offset param if offset is less than 1", %{conn: conn} do
+ address = insert(:address)
+
+ 6
+ |> insert_list(:transaction, from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}",
+ # page number
+ "page" => "1",
+ # page size
+ "offset" => "0"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert length(response["result"]) == 6
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "ignores offset param if offset is over 10,000", %{conn: conn} do
+ address = insert(:address)
+
+ 6
+ |> insert_list(:transaction, from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}",
+ # page number
+ "page" => "1",
+ # page size
+ "offset" => "10_500"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert length(response["result"]) == 6
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "with page number with no results", %{conn: conn} do
+ address = insert(:address)
+
+ _transactions_1 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ _transactions_2 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ _transactions_3 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}",
+ # page number
+ "page" => "5",
+ # page size
+ "offset" => "2"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"] == []
+ assert response["status"] == "0"
+ assert response["message"] == "No transactions found"
+ assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response)
+ end
+
+ test "supports GET and POST requests", %{conn: conn} do
+ address = insert(:address)
+
+ :transaction
+ |> insert(from_address: address)
+
+ params = %{
+ "module" => "account",
+ "action" => "pendingtxlist",
+ "address" => "#{address.hash}"
+ }
+
+ assert get_response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert post_response =
+ conn
+ |> post("/api", params)
+ |> json_response(200)
+
+ assert get_response == post_response
+ end
+ end
+
describe "txlistinternal" do
test "with missing txhash and address", %{conn: conn} do
params = %{
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
index 2b3f6fff5e..d46071d215 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
@@ -1,6 +1,8 @@
defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
use BlockScoutWeb.ConnCase
+ import Mox
+
@moduletag capture_log: true
describe "gettxreceiptstatus" do
@@ -520,7 +522,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"index" => "#{log.index}"
}
],
- "next_page_params" => nil
+ "next_page_params" => nil,
+ "revertReason" => ""
}
schema =
@@ -576,6 +579,184 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["status"] == "1"
assert response["message"] == "OK"
end
+
+ test "with a txhash with revert reason from DB", %{conn: conn} do
+ block = insert(:block, number: 100)
+
+ transaction =
+ :transaction
+ |> insert(revert_reason: "No credit of that type")
+ |> with_block(block)
+
+ insert(:address)
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == "No credit of that type"
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with empty revert reason from DB", %{conn: conn} do
+ block = insert(:block, number: 100)
+
+ transaction =
+ :transaction
+ |> insert(revert_reason: "")
+ |> with_block(block)
+
+ insert(:address)
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with revert reason from the archive node", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == "No credit of that type"
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+ end
+
+ test "with a txhash with empty revert reason from the archive node", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: ""}}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with empty revert reason from DB if eth_call doesn't return an error", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:ok}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
end
defp resolve_schema(result \\ %{}) do
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
index 5e4f723b75..ec2565ed1c 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
@@ -22,7 +22,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
test "error for invalid address" do
- path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00")
+ path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00", type: :regular, action: :read)
conn =
build_conn()
@@ -49,7 +49,12 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
blockchain_get_function_mock()
- path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash)
+ path =
+ smart_contract_path(BlockScoutWeb.Endpoint, :index,
+ hash: token_contract_address.hash,
+ type: :regular,
+ action: :read
+ )
conn =
build_conn()
@@ -59,6 +64,112 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
assert conn.status == 200
refute conn.assigns.read_only_functions == []
end
+
+ test "lists [] proxy read only functions if no verified implementation" do
+ token_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: token_contract_address.hash,
+ abi: [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ }
+ ]
+ )
+
+ path =
+ smart_contract_path(BlockScoutWeb.Endpoint, :index,
+ hash: token_contract_address.hash,
+ type: :proxy,
+ action: :read
+ )
+
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get(path)
+
+ assert conn.status == 200
+ assert conn.assigns.read_only_functions == []
+ end
+
+ test "lists [] proxy read only functions if no verified eip-1967 implementation" do
+ token_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: token_contract_address.hash,
+ abi: [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => false
+ }
+ ]
+ )
+
+ blockchain_get_implementation_mock()
+
+ path =
+ smart_contract_path(BlockScoutWeb.Endpoint, :index,
+ hash: token_contract_address.hash,
+ type: :proxy,
+ action: :read
+ )
+
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get(path)
+
+ assert conn.status == 200
+ assert conn.assigns.read_only_functions == []
+ end
+
+ test "lists [] proxy read only functions if no verified eip-1967 implementation and eth_getStorageAt returns not nnormalized address hash" do
+ token_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: token_contract_address.hash,
+ abi: [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => false
+ }
+ ]
+ )
+
+ blockchain_get_implementation_mock_2()
+
+ path =
+ smart_contract_path(BlockScoutWeb.Endpoint, :index,
+ hash: token_contract_address.hash,
+ type: :proxy,
+ action: :read
+ )
+
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get(path)
+
+ assert conn.status == 200
+ assert conn.assigns.read_only_functions == []
+ end
end
describe "GET show/3" do
@@ -156,4 +267,24 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
)
end
+
+ defp blockchain_get_implementation_mock do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn %{id: _, method: _, params: [_, _, _]}, _options ->
+ {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
+ end
+ )
+ end
+
+ defp blockchain_get_implementation_mock_2 do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn %{id: _, method: _, params: [_, _, _]}, _options ->
+ {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
+ end
+ )
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs
new file mode 100644
index 0000000000..cf1b6fd8e9
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs
@@ -0,0 +1,19 @@
+defmodule BlockScoutWeb.AddressReadProxyViewTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias BlockScoutWeb.AddressReadProxyView
+
+ describe "queryable?/1" do
+ test "returns true if list of inputs is not empty" do
+ assert AddressReadProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
+ assert AddressReadProxyView.queryable?([]) == false
+ end
+ end
+
+ describe "address?/1" do
+ test "returns true if type equals `address`" do
+ assert AddressReadProxyView.address?("address") == true
+ assert AddressReadProxyView.address?("uint256") == false
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs
new file mode 100644
index 0000000000..55360f1229
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs
@@ -0,0 +1,19 @@
+defmodule BlockScoutWeb.AddressWriteContractViewTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias BlockScoutWeb.AddressWriteContractView
+
+ describe "queryable?/1" do
+ test "returns true if list of inputs is not empty" do
+ assert AddressWriteContractView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
+ assert AddressWriteContractView.queryable?([]) == false
+ end
+ end
+
+ describe "address?/1" do
+ test "returns true if type equals `address`" do
+ assert AddressWriteContractView.address?("address") == true
+ assert AddressWriteContractView.address?("uint256") == false
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs b/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs
new file mode 100644
index 0000000000..e3db0d7003
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs
@@ -0,0 +1,19 @@
+defmodule BlockScoutWeb.AddressWriteProxyViewTest do
+ use BlockScoutWeb.ConnCase, async: true
+
+ alias BlockScoutWeb.AddressWriteProxyView
+
+ describe "queryable?/1" do
+ test "returns true if list of inputs is not empty" do
+ assert AddressWriteProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
+ assert AddressWriteProxyView.queryable?([]) == false
+ end
+ end
+
+ describe "address?/1" do
+ test "returns true if type equals `address`" do
+ assert AddressWriteProxyView.address?("address") == true
+ assert AddressWriteProxyView.address?("uint256") == false
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
index 5ab8133bf5..cc812cd41b 100644
--- a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
@@ -17,6 +17,183 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end
end
+ describe "writeable?" do
+ test "returns true when there is write function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
+ "constant" => false
+ }
+
+ assert SmartContractView.writeable?(function)
+ end
+
+ test "returns false when it is not a write function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ }
+
+ refute SmartContractView.writeable?(function)
+ end
+
+ test "returns false when there is no function" do
+ function = %{}
+
+ refute SmartContractView.writeable?(function)
+ end
+
+ test "returns false when there function is nil" do
+ function = nil
+
+ refute SmartContractView.writeable?(function)
+ end
+ end
+
+ describe "outputs?" do
+ test "returns true when there are outputs" do
+ outputs = [%{"name" => "_narcoId", "type" => "uint256"}]
+
+ assert SmartContractView.outputs?(outputs)
+ end
+
+ test "returns false when there are no outputs" do
+ outputs = []
+
+ refute SmartContractView.outputs?(outputs)
+ end
+ end
+
+ describe "payable?" do
+ test "returns true when there is payable function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "payable" => true,
+ "outputs" => [],
+ "name" => "upgradeToAndCall",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"},
+ %{"type" => "bytes", "name" => "data"}
+ ],
+ "constant" => false
+ }
+
+ assert SmartContractView.payable?(function)
+ end
+
+ test "returns true when there is old-style payable function" do
+ function = %{
+ "type" => "function",
+ "payable" => true,
+ "outputs" => [],
+ "name" => "upgradeToAndCall",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"},
+ %{"type" => "bytes", "name" => "data"}
+ ],
+ "constant" => false
+ }
+
+ assert SmartContractView.payable?(function)
+ end
+
+ test "returns false when it is nonpayable function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferProxyOwnership",
+ "inputs" => [%{"type" => "address", "name" => "newOwner"}],
+ "constant" => false
+ }
+
+ refute SmartContractView.payable?(function)
+ end
+
+ test "returns false when there is no function" do
+ function = %{}
+
+ refute SmartContractView.payable?(function)
+ end
+
+ test "returns false when function is nil" do
+ function = nil
+
+ refute SmartContractView.payable?(function)
+ end
+ end
+
+ describe "nonpayable?" do
+ test "returns true when there is nonpayable function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferProxyOwnership",
+ "inputs" => [%{"type" => "address", "name" => "newOwner"}],
+ "constant" => false
+ }
+
+ assert SmartContractView.nonpayable?(function)
+ end
+
+ test "returns true when there is old-style nonpayable function" do
+ function = %{
+ "type" => "function",
+ "outputs" => [],
+ "name" => "test",
+ "inputs" => [%{"type" => "address", "name" => "newOwner"}],
+ "constant" => false
+ }
+
+ assert SmartContractView.nonpayable?(function)
+ end
+
+ test "returns false when it is payable function" do
+ function = %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "payable" => true,
+ "outputs" => [],
+ "name" => "upgradeToAndCall",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"},
+ %{"type" => "bytes", "name" => "data"}
+ ],
+ "constant" => false
+ }
+
+ refute SmartContractView.nonpayable?(function)
+ end
+
+ test "returns true when there is no function" do
+ function = %{}
+
+ refute SmartContractView.nonpayable?(function)
+ end
+
+ test "returns false when function is nil" do
+ function = nil
+
+ refute SmartContractView.nonpayable?(function)
+ end
+ end
+
describe "address?" do
test "returns true when the type is equal to the string 'address'" do
type = "address"
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
new file mode 100644
index 0000000000..4e834d78a8
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
@@ -0,0 +1,305 @@
+# credo:disable-for-this-file
+defmodule EthereumJSONRPC.Besu do
+ @moduledoc """
+ Ethereum JSONRPC methods that are only supported by [Besu](https://besu.hyperledger.org/en/stable/Reference/API-Methods).
+ """
+ require Logger
+ import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
+
+ alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces}
+ alias EthereumJSONRPC.{Transaction, Transactions}
+
+ @behaviour EthereumJSONRPC.Variant
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_beneficiaries(block_numbers, json_rpc_named_arguments)
+ when is_list(block_numbers) and is_list(json_rpc_named_arguments) do
+ id_to_params =
+ block_numbers
+ |> block_numbers_to_params_list()
+ |> id_to_params()
+
+ with {:ok, responses} <-
+ id_to_params
+ |> FetchedBeneficiaries.requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)}
+ end
+ end
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
+
+ @doc """
+ Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Besu trace URL.
+ """
+ @impl EthereumJSONRPC.Variant
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
+ id_to_params = id_to_params(block_numbers)
+
+ with {:ok, responses} <-
+ id_to_params
+ |> trace_replay_block_transactions_requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ end
+ end
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
+ id_to_params = id_to_params(transactions_params)
+
+ trace_replay_transaction_response =
+ id_to_params
+ |> trace_replay_transaction_requests()
+ |> json_rpc(json_rpc_named_arguments)
+
+ case trace_replay_transaction_response do
+ {:ok, responses} ->
+ case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do
+ {:ok, [first_trace]} ->
+ %{block_hash: block_hash} =
+ transactions_params
+ |> Enum.at(0)
+
+ {:ok,
+ [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]}
+
+ {:error, error} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+
+ {:error, :econnrefused} ->
+ {:error, :econnrefused}
+
+ {:error, [error]} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+ end
+
+ @doc """
+ Fetches the pending transactions from the Besu node.
+
+ *NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based
+ on the transactions that each node has seen and how each node prioritizes collating transactions into the next block.
+ """
+ @impl EthereumJSONRPC.Variant
+ @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
+ {:ok, [Transaction.params()]} | {:error, reason :: term}
+ def fetch_pending_transactions(json_rpc_named_arguments) do
+ with {:ok, transactions} <-
+ %{id: 1, method: "txpool_besuTransactions", params: []}
+ |> request()
+ |> json_rpc(json_rpc_named_arguments) do
+ transactions_params =
+ transactions
+ |> Transactions.to_elixir()
+ |> Transactions.elixir_to_params()
+
+ {:ok, transactions_params}
+ end
+ end
+
+ defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do
+ Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
+ end
+
+ defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params))
+ |> Enum.reduce(
+ {:ok, []},
+ fn
+ {:ok, traces}, {:ok, acc_traces_list} ->
+ {:ok, [traces | acc_traces_list]}
+
+ {:ok, _}, {:error, _} = acc_error ->
+ acc_error
+
+ {:error, reason}, {:ok, _} ->
+ {:error, [reason]}
+
+ {:error, reason}, {:error, acc_reason} ->
+ {:error, [reason | acc_reason]}
+ end
+ )
+ |> case do
+ {:ok, traces_list} ->
+ traces =
+ traces_list
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {:ok, traces}
+
+ {:error, reverse_reasons} ->
+ reasons = Enum.reverse(reverse_reasons)
+ {:error, reasons}
+ end
+ end
+
+ defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
+ when is_list(results) and is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_traces =
+ results
+ |> Stream.with_index()
+ |> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockNumber" => block_number,
+ "transactionHash" => transaction_hash,
+ "transactionIndex" => transaction_index,
+ "index" => index
+ })
+ end)
+ end)
+
+ {:ok, annotated_traces}
+ end
+
+ defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockNumber" => block_number
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, block_number} ->
+ trace_replay_block_transactions_request(%{id: id, block_number: block_number})
+ end)
+ end
+
+ defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
+ request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
+ end
+
+ def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params))
+ |> Enum.reduce(
+ {:ok, []},
+ fn
+ {:ok, traces}, {:ok, acc_traces_list} ->
+ {:ok, [traces | acc_traces_list]}
+
+ {:ok, _}, {:error, _} = acc_error ->
+ acc_error
+
+ {:error, reason}, {:ok, _} ->
+ {:error, [reason]}
+
+ {:error, reason}, {:error, acc_reason} ->
+ {:error, [reason | acc_reason]}
+ end
+ )
+ |> case do
+ {:ok, traces_list} ->
+ traces =
+ traces_list
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {:ok, traces}
+
+ {:error, reverse_reasons} ->
+ reasons = Enum.reverse(reverse_reasons)
+ {:error, reasons}
+ end
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params)
+ when is_list(traces) and is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ first_trace =
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "index" => index,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+ end)
+ |> Enum.filter(fn trace ->
+ Map.get(trace, "index") == 0
+ end)
+
+ {:ok, first_trace}
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
+ trace_replay_transaction_request(%{id: id, hash_data: hash_data})
+ end)
+ end
+
+ defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
+ request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
new file mode 100644
index 0000000000..cb569cf9f2
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
@@ -0,0 +1,196 @@
+defmodule EthereumJSONRPC.Besu.FetchedBeneficiaries do
+ @moduledoc """
+ Beneficiaries and errors from batch requests to `trace_block`.
+ """
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+
+ @doc """
+ Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`.
+
+ responses - List with trace_block responses
+ id_to_params - Maps request id to query params
+
+ ## Examples
+ iex> EthereumJSONRPC.Besu.FetchedBeneficiaries.from_responses(
+ ...> [
+ ...> %{
+ ...> id: 0,
+ ...> result: [
+ ...> %{
+ ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> },
+ ...> %{
+ ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> }
+ ...> ]
+ ...> }
+ ...> ],
+ ...> %{0 => %{block_quantity: "0xC"}}
+ ...> )
+ %EthereumJSONRPC.FetchedBeneficiaries{
+ errors: [],
+ params_set: #MapSet<[
+ %{
+ address_hash: "0x1",
+ address_type: :validator,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ },
+ %{
+ address_hash: "0x2",
+ address_type: :emission_funds,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ }
+ ]>
+ }
+ """
+ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&response_to_params_set(&1, id_to_params))
+ |> Enum.reduce(
+ %EthereumJSONRPC.FetchedBeneficiaries{},
+ fn
+ {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)}
+
+ {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]}
+ end
+ )
+ end
+
+ @doc """
+ `trace_block` requests for `id_to_params`.
+ """
+ def requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity}} ->
+ request(%{id: id, block_quantity: block_quantity})
+ end)
+ end
+
+ @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) ::
+ {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}}
+ end
+
+ @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) ::
+ {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+ block_number = quantity_to_integer(block_quantity)
+ params_set = traces_to_params_set(traces, block_number)
+
+ {:ok, params_set}
+ end
+
+ @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{
+ id => %{block_quantity: block_quantity}
+ }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ annotated_error = Map.put(error, :data, %{block_quantity: block_quantity})
+
+ {:error, annotated_error}
+ end
+
+ defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do
+ EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]})
+ end
+
+ defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do
+ traces
+ |> Stream.filter(&(&1["type"] == "reward"))
+ |> Stream.with_index()
+ |> Enum.reduce(MapSet.new(), fn {trace, index}, acc ->
+ MapSet.union(acc, trace_to_params_set(trace, block_number, index))
+ end)
+ end
+
+ defp trace_to_params_set(
+ %{
+ "action" => %{
+ "rewardType" => reward_type,
+ "author" => address_hash_data,
+ "value" => reward_value
+ },
+ "blockHash" => block_hash,
+ "blockNumber" => block_number
+ },
+ block_number,
+ index
+ )
+ when is_integer(block_number) and reward_type in ~w(block external uncle) do
+ MapSet.new([
+ %{
+ address_hash: address_hash_data,
+ block_hash: block_hash,
+ block_number: block_number,
+ reward: reward_value,
+ address_type: get_address_type(reward_type, index)
+ }
+ ])
+ end
+
+ # Beneficiary's address type will depend on the responses' action.rewardType,
+ # which will vary depending on which network is being indexed
+ #
+ # On POA networks, rewardType will always be external and the type of the address being
+ # rewarded will depend on its position.
+ # First address will always be the validator's while the second will be the EmissionsFunds address
+ #
+ # On PoW networks, like Ethereum, the reward type will already specify the type for the
+ # address being rewarded
+ # The rewardType "block" will show the reward for the consensus block validator
+ # The rewardType "uncle" will show reward for validating an uncle block
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 5, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 6, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 7, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 8, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 9, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 10, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 11, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 12, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 13, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 14, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 15, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 16, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 17, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 18, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 19, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 20, do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle
+ defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
index e1c60f7352..ed0e6d614f 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
@@ -41,7 +41,7 @@ defmodule EthereumJSONRPC.Contract do
|> Enum.map(fn {%{contract_address: contract_address, function_name: function_name, args: args} = request, index} ->
functions[function_name]
|> Encoder.encode_function_call(args)
- |> eth_call_request(contract_address, index, Map.get(request, :block_number))
+ |> eth_call_request(contract_address, index, Map.get(request, :block_number), Map.get(request, :from))
end)
|> json_rpc(json_rpc_named_arguments)
|> case do
@@ -70,7 +70,7 @@ defmodule EthereumJSONRPC.Contract do
Enum.map(requests, fn _ -> format_error(error) end)
end
- defp eth_call_request(data, contract_address, id, block_number) do
+ defp eth_call_request(data, contract_address, id, block_number, from) do
block =
case block_number do
nil -> "latest"
@@ -80,10 +80,28 @@ defmodule EthereumJSONRPC.Contract do
request(%{
id: id,
method: "eth_call",
- params: [%{to: contract_address, data: data}, block]
+ params: [%{to: contract_address, data: data, from: from}, block]
})
end
+ def eth_get_storage_at_request(contract_address, storage_pointer, block_number, json_rpc_named_arguments) do
+ block =
+ case block_number do
+ nil -> "latest"
+ block_number -> integer_to_quantity(block_number)
+ end
+
+ result =
+ %{id: 0, method: "eth_getStorageAt", params: [contract_address, storage_pointer, block]}
+ |> request()
+ |> json_rpc(json_rpc_named_arguments)
+
+ case result do
+ {:ok, storage_value} -> {:ok, storage_value}
+ other -> other
+ end
+ end
+
defp format_error(message) when is_binary(message) do
{:error, message}
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
index 03e8c8457c..05e32348f2 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
@@ -38,7 +38,7 @@ defmodule EthereumJSONRPC.Geth do
end
@doc """
- Fetches the first trace from the Parity trace URL.
+ Fetches the first trace from the trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
index 84a1b94702..8165651ad5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
@@ -1,3 +1,4 @@
+# credo:disable-for-this-file
defmodule EthereumJSONRPC.Parity do
@moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
index cf1a74954a..8a7cd5f384 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
@@ -253,7 +253,7 @@ defmodule EthereumJSONRPC.Receipt do
# hash format
# gas is passed in from the `t:EthereumJSONRPC.Transaction.params/0` to allow pre-Byzantium status to be derived
defp entry_to_elixir({key, _} = entry)
- when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash),
+ when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash revertReason),
do: {:ok, entry}
defp entry_to_elixir({key, quantity})
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
index 031b150384..54d812c4c4 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
@@ -9,7 +9,7 @@ defmodule EthereumJSONRPC.Transaction do
"""
require Logger
- import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ import EthereumJSONRPC, only: [quantity_to_integer: 1, integer_to_quantity: 1, request: 1]
alias EthereumJSONRPC
@@ -313,6 +313,20 @@ defmodule EthereumJSONRPC.Transaction do
nil
end
+ def eth_call_request(id, block_number, data, to, from, gas, gas_price, value) do
+ block =
+ case block_number do
+ nil -> "latest"
+ block_number -> integer_to_quantity(block_number)
+ end
+
+ request(%{
+ id: id,
+ method: "eth_call",
+ params: [%{to: to, from: from, data: data, gas: gas, gas_price: gas_price, value: value}, block]
+ })
+ end
+
# double check that no new keys are being missed by requiring explicit match for passthrough
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 8424590e95..57d5030cf5 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -207,6 +207,11 @@ config :explorer, Explorer.Chain.Cache.Accounts,
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.PendingTransactions,
+ enabled:
+ if(System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu",
+ do: false,
+ else: true
+ ),
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs
new file mode 100644
index 0000000000..e014ac960c
--- /dev/null
+++ b/apps/explorer/config/dev/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs
new file mode 100644
index 0000000000..4b69e1363e
--- /dev/null
+++ b/apps/explorer/config/prod/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/test/besu.exs b/apps/explorer/config/test/besu.exs
new file mode 100644
index 0000000000..f5c234c7e1
--- /dev/null
+++ b/apps/explorer/config/test/besu.exs
@@ -0,0 +1,14 @@
+use Mix.Config
+
+config :explorer,
+ transport: EthereumJSONRPC.HTTP,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index f89e94a365..9a2adccf3e 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -28,6 +28,9 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}
+ alias EthereumJSONRPC.Contract
+ alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
+
alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain
@@ -72,6 +75,7 @@ defmodule Explorer.Chain do
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
+ alias Explorer.SmartContract.Reader
alias Dataloader.Ecto, as: DataloaderEcto
@@ -2446,7 +2450,7 @@ defmodule Explorer.Chain do
|> Repo.all()
end
- defp pending_transactions_query(query) do
+ def pending_transactions_query(query) do
from(transaction in query,
where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced")
)
@@ -2715,6 +2719,68 @@ defmodule Explorer.Chain do
def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error}
+ def transaction_to_revert_reason(transaction) do
+ %Transaction{revert_reason: revert_reason} = transaction
+
+ if revert_reason == nil do
+ fetch_tx_revert_reason(transaction)
+ else
+ revert_reason
+ end
+ end
+
+ def fetch_tx_revert_reason(
+ %Transaction{
+ block_number: block_number,
+ to_address_hash: to_address_hash,
+ from_address_hash: from_address_hash,
+ input: data,
+ gas: gas,
+ gas_price: gas_price,
+ value: value
+ } = transaction
+ ) do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ req =
+ EthereumJSONRPCTransaction.eth_call_request(
+ 0,
+ block_number,
+ data,
+ to_address_hash,
+ from_address_hash,
+ Decimal.to_integer(gas),
+ Wei.hex_format(gas_price),
+ Wei.hex_format(value)
+ )
+
+ data =
+ case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do
+ {:error, %{data: data}} ->
+ data
+
+ _ ->
+ ""
+ end
+
+ revert_reason_parts = String.split(data, "revert: ")
+
+ formatted_revert_reason =
+ if Enum.count(revert_reason_parts) > 1 do
+ Enum.at(revert_reason_parts, 1)
+ else
+ data
+ end
+
+ if byte_size(formatted_revert_reason) > 0 do
+ transaction
+ |> Changeset.change(%{revert_reason: formatted_revert_reason})
+ |> Repo.update()
+ end
+
+ formatted_revert_reason
+ end
+
@doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
@@ -3499,56 +3565,63 @@ defmodule Explorer.Chain do
|> page_coin_balances(paging_options)
|> Repo.all()
- min_block_number =
- balances_raw
- |> Enum.min_by(fn balance -> balance.block_number end)
- |> Map.get(:block_number)
-
- max_block_number =
+ if Enum.empty?(balances_raw) do
balances_raw
- |> Enum.max_by(fn balance -> balance.block_number end)
- |> Map.get(:block_number)
-
- min_block_timestamp = find_block_timestamp(min_block_number)
- max_block_timestamp = find_block_timestamp(max_block_number)
-
- min_block_unix_timestamp =
- min_block_timestamp
- |> Timex.to_unix()
-
- max_block_unix_timestamp =
- max_block_timestamp
- |> Timex.to_unix()
-
- blocks_delta = max_block_number - min_block_number
-
- balances_with_dates =
- if blocks_delta > 0 do
- balances_raw
- |> Enum.map(fn balance ->
- date =
- trunc(
- min_block_unix_timestamp +
- (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) /
- blocks_delta
- )
-
- formatted_date = Timex.from_unix(date)
- %{balance | block_timestamp: formatted_date}
- end)
- else
+ else
+ balances_raw_filtered =
balances_raw
- |> Enum.map(fn balance ->
- date = min_block_unix_timestamp
+ |> Enum.filter(fn balance -> balance.value end)
+
+ min_block_number =
+ balances_raw_filtered
+ |> Enum.min_by(fn balance -> balance.block_number end, fn -> %{} end)
+ |> Map.get(:block_number)
+
+ max_block_number =
+ balances_raw_filtered
+ |> Enum.max_by(fn balance -> balance.block_number end, fn -> %{} end)
+ |> Map.get(:block_number)
+
+ min_block_timestamp = find_block_timestamp(min_block_number)
+ max_block_timestamp = find_block_timestamp(max_block_number)
+
+ min_block_unix_timestamp =
+ min_block_timestamp
+ |> Timex.to_unix()
+
+ max_block_unix_timestamp =
+ max_block_timestamp
+ |> Timex.to_unix()
+
+ blocks_delta = max_block_number - min_block_number
+
+ balances_with_dates =
+ if blocks_delta > 0 do
+ balances_raw_filtered
+ |> Enum.map(fn balance ->
+ date =
+ trunc(
+ min_block_unix_timestamp +
+ (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) /
+ blocks_delta
+ )
+
+ formatted_date = Timex.from_unix(date)
+ %{balance | block_timestamp: formatted_date}
+ end)
+ else
+ balances_raw_filtered
+ |> Enum.map(fn balance ->
+ date = min_block_unix_timestamp
- formatted_date = Timex.from_unix(date)
- %{balance | block_timestamp: formatted_date}
- end)
- end
+ formatted_date = Timex.from_unix(date)
+ %{balance | block_timestamp: formatted_date}
+ end)
+ end
- balances_with_dates
- |> Enum.filter(fn balance -> balance.value end)
- |> Enum.sort(fn balance1, balance2 -> balance1.block_timestamp >= balance2.block_timestamp end)
+ balances_with_dates
+ |> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end)
+ end
end
def get_coin_balance(address_hash, block_number) do
@@ -4266,6 +4339,129 @@ defmodule Explorer.Chain do
end
end
+ def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do
+ implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi)
+
+ if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
+ end
+
+ def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
+ []
+ end
+
+ def is_proxy_contract?(abi) when not is_nil(abi) do
+ implementation_method_abi =
+ abi
+ |> Enum.find(fn method ->
+ Map.get(method, "name") == "implementation"
+ end)
+
+ if implementation_method_abi, do: true, else: false
+ end
+
+ def is_proxy_contract?(abi) when is_nil(abi) do
+ false
+ end
+
+ def get_implementation_address_hash(proxy_address_hash, abi)
+ when not is_nil(proxy_address_hash) and not is_nil(abi) do
+ implementation_method_abi =
+ abi
+ |> Enum.find(fn method ->
+ Map.get(method, "name") == "implementation"
+ end)
+
+ implementation_method_abi_state_mutability = Map.get(implementation_method_abi, "stateMutability")
+ is_eip1967 = if implementation_method_abi_state_mutability == "nonpayable", do: true, else: false
+
+ if is_eip1967 do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ # https://eips.ethereum.org/EIPS/eip-1967
+ eip_1967_implementation_storage_pointer = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
+
+ {:ok, implementation_address} =
+ Contract.eth_get_storage_at_request(
+ proxy_address_hash,
+ eip_1967_implementation_storage_pointer,
+ nil,
+ json_rpc_named_arguments
+ )
+
+ if String.length(implementation_address) > 42 do
+ "0x" <> String.slice(implementation_address, -40, 40)
+ else
+ implementation_address
+ end
+ else
+ implementation_address =
+ case Reader.query_contract(proxy_address_hash, abi, %{
+ "implementation" => []
+ }) do
+ %{"implementation" => {:ok, [result]}} -> result
+ _ -> nil
+ end
+
+ if implementation_address do
+ "0x" <> Base.encode16(implementation_address, case: :lower)
+ else
+ nil
+ end
+ end
+ end
+
+ def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
+ nil
+ end
+
+ def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do
+ case Chain.string_to_address_hash(implementation_address_hash_string) do
+ {:ok, implementation_address_hash} ->
+ implementation_smart_contract =
+ implementation_address_hash
+ |> Chain.address_hash_to_smart_contract()
+
+ if implementation_smart_contract do
+ implementation_smart_contract
+ |> Map.get(:abi)
+ else
+ []
+ end
+
+ _ ->
+ []
+ end
+ end
+
+ def get_implementation_abi(implementation_address_hash_string) when is_nil(implementation_address_hash_string) do
+ []
+ end
+
+ def get_implementation_abi_from_proxy(proxy_address_hash, abi)
+ when not is_nil(proxy_address_hash) and not is_nil(abi) do
+ implementation_method_abi =
+ abi
+ |> Enum.find(fn method ->
+ Map.get(method, "name") == "implementation"
+ end)
+
+ if implementation_method_abi do
+ implementation_address_hash_string = get_implementation_address_hash(proxy_address_hash, abi)
+
+ if implementation_address_hash_string do
+ get_implementation_abi(implementation_address_hash_string)
+ else
+ []
+ end
+ else
+ []
+ end
+ end
+
+ def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
+ []
+ end
+
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do
diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance.ex b/apps/explorer/lib/explorer/chain/address/coin_balance.ex
index ca693b84a8..cada366173 100644
--- a/apps/explorer/lib/explorer/chain/address/coin_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/coin_balance.ex
@@ -72,13 +72,18 @@ defmodule Explorer.Chain.Address.CoinBalance do
The last coin balance from an Address is the last block indexed.
"""
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
- from(
- cb in CoinBalance,
- where: cb.address_hash == ^address_hash,
- where: not is_nil(cb.value),
- order_by: [desc: :block_number],
- limit: ^page_size,
- select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")}
+ query =
+ from(
+ cb in CoinBalance,
+ where: cb.address_hash == ^address_hash,
+ where: not is_nil(cb.value),
+ order_by: [desc: :block_number],
+ select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")}
+ )
+
+ from(balance in subquery(query),
+ where: balance.delta != 0,
+ limit: ^page_size
)
end
@@ -87,21 +92,40 @@ defmodule Explorer.Chain.Address.CoinBalance do
corresponds to the maximum balance in that day. Only the last 90 days of data are used.
"""
def balances_by_day(address_hash, block_timestamp \\ nil) do
+ {days_to_consider, _} =
+ Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days]
+ |> Integer.parse()
+
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
- |> limit_time_interval(block_timestamp)
+ |> limit_time_interval(days_to_consider, block_timestamp)
|> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end
- def limit_time_interval(query, nil) do
- query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'"))
+ def limit_time_interval(query, days_to_consider, nil) do
+ query
+ |> where(
+ [cb, b],
+ b.timestamp >=
+ fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
+ )
end
- def limit_time_interval(query, %{timestamp: timestamp}) do
- query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC"))
+ def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
+ query
+ |> where(
+ [cb, b],
+ b.timestamp >=
+ fragment(
+ "(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
+ ^timestamp,
+ ^"Etc/UTC",
+ ^%Postgrex.Interval{days: days_to_consider}
+ )
+ )
end
def last_coin_balance_timestamp(address_hash) do
diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex
index 27899303f3..7887fb0be0 100644
--- a/apps/explorer/lib/explorer/chain/log.ex
+++ b/apps/explorer/lib/explorer/chain/log.ex
@@ -6,8 +6,8 @@ defmodule Explorer.Chain.Log do
require Logger
alias ABI.{Event, FunctionSelector}
+ alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
- alias Explorer.Repo
@required_attrs ~w(address_hash data block_hash index transaction_hash)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
@@ -119,22 +119,35 @@ defmodule Explorer.Chain.Log do
@doc """
Decode transaction log data.
"""
- def decode(_log, %Transaction{to_address: nil}), do: {:error, :no_to_address}
- def decode(log, transaction = %Transaction{to_address: %{smart_contract: %{abi: abi}}}) when not is_nil(abi) do
- with {:ok, selector, mapping} <- find_and_decode(abi, log, transaction),
- identifier <- Base.encode16(selector.method_id, case: :lower),
- text <- function_call(selector.function, mapping),
- do: {:ok, identifier, text, mapping}
+ def decode(log, transaction) do
+ address_options = [
+ necessity_by_association: %{
+ :smart_contract => :optional
+ }
+ ]
+
+ case Chain.find_contract_address(log.address_hash, address_options, true) do
+ {:ok, %{smart_contract: %{abi: abi}}} ->
+ full_abi = Chain.combine_proxy_implementation_abi(log.address_hash, abi)
+
+ with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
+ identifier <- Base.encode16(selector.method_id, case: :lower),
+ text <- function_call(selector.function, mapping),
+ do: {:ok, identifier, text, mapping}
+
+ _ ->
+ find_candidates(log, transaction)
+ end
end
- def decode(log, transaction) do
+ defp find_candidates(log, transaction) do
case log.first_topic do
"0x" <> hex_part ->
case Integer.parse(hex_part, 16) do
{number, ""} ->
<
> = :binary.encode_unsigned(number)
- find_candidates(method_id, log, transaction)
+ find_candidates_query(method_id, log, transaction)
_ ->
{:error, :could_not_decode}
@@ -145,7 +158,7 @@ defmodule Explorer.Chain.Log do
end
end
- defp find_candidates(method_id, log, transaction) do
+ defp find_candidates_query(method_id, log, transaction) do
candidates_query =
from(
contract_method in ContractMethod,
diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex
index 9d4c03134a..72c2d78b2b 100644
--- a/apps/explorer/lib/explorer/chain/smart_contract.ex
+++ b/apps/explorer/lib/explorer/chain/smart_contract.ex
@@ -255,20 +255,26 @@ defmodule Explorer.Chain.SmartContract do
|> prepare_changes(&upsert_contract_methods/1)
end
- def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error) do
- smart_contract
- |> cast(attrs, [
- :name,
- :compiler_version,
- :optimization,
- :contract_source_code,
- :address_hash,
- :evm_version,
- :optimization_runs,
- :constructor_arguments
- ])
- |> validate_required([:name, :compiler_version, :optimization, :address_hash])
- |> add_error(:contract_source_code, error_message(error))
+ def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error, error_message) do
+ validated =
+ smart_contract
+ |> cast(attrs, [
+ :name,
+ :compiler_version,
+ :optimization,
+ :contract_source_code,
+ :address_hash,
+ :evm_version,
+ :optimization_runs,
+ :constructor_arguments
+ ])
+ |> validate_required([:name, :compiler_version, :optimization, :address_hash])
+
+ if error_message do
+ add_error(validated, :contract_source_code, error_message(error, error_message))
+ else
+ add_error(validated, :contract_source_code, error_message(error))
+ end
end
def add_submitted_comment(code, inserted_at) when is_binary(code) do
@@ -331,4 +337,5 @@ defmodule Explorer.Chain.SmartContract do
defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again."
defp error_message(:name), do: "Wrong contract name, please try again."
defp error_message(_), do: "There was an error validating your contract, please try again."
+ defp error_message(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}"
end
diff --git a/apps/explorer/lib/explorer/chain/supply/token_bridge.ex b/apps/explorer/lib/explorer/chain/supply/token_bridge.ex
index 14e7a0a6e1..42ed6c2201 100644
--- a/apps/explorer/lib/explorer/chain/supply/token_bridge.ex
+++ b/apps/explorer/lib/explorer/chain/supply/token_bridge.ex
@@ -85,12 +85,6 @@ defmodule Explorer.Chain.Supply.TokenBridge do
|> Enum.map(fn {key, _value} -> key end)
|> List.first()
- value =
- case Reader.query_contract(address, abi, params) do
- %{^method_name => {:ok, [result]}} -> result
- _ -> 0
- end
-
type =
abi
|> Enum.at(0)
@@ -98,6 +92,19 @@ defmodule Explorer.Chain.Supply.TokenBridge do
|> Enum.at(0)
|> Map.get("type", "")
+ value =
+ case Reader.query_contract(address, abi, params) do
+ %{^method_name => {:ok, [result]}} ->
+ result
+
+ _ ->
+ case type do
+ "address" -> "0x0000000000000000000000000000000000000000"
+ "uint256" -> 0
+ _ -> 0
+ end
+ end
+
case type do
"address" ->
"0x" <> Base.encode16(value)
diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex
index 66127868c6..e4518c7349 100644
--- a/apps/explorer/lib/explorer/chain/transaction.ex
+++ b/apps/explorer/lib/explorer/chain/transaction.ex
@@ -11,6 +11,8 @@ defmodule Explorer.Chain.Transaction do
alias Ecto.Changeset
+ alias Explorer.{Chain, Repo}
+
alias Explorer.Chain.{
Address,
Block,
@@ -26,11 +28,10 @@ defmodule Explorer.Chain.Transaction do
}
alias Explorer.Chain.Transaction.{Fork, Status}
- alias Explorer.Repo
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index created_contract_code_indexed_at status
- to_address_hash)a
+ to_address_hash revert_reason)a
@required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
@@ -106,6 +107,7 @@ defmodule Explorer.Chain.Transaction do
* `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer`
+ * `revert_reason` - revert reason of transaction
| `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description |
|----------|----------------------------------|------------|-----------------|-------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------|
@@ -129,6 +131,7 @@ defmodule Explorer.Chain.Transaction do
* `uncles` - uncle blocks where `forks` were collated
* `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address`
+ * `revert_reason` - revert reason of transaction
"""
@type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
@@ -159,7 +162,8 @@ defmodule Explorer.Chain.Transaction do
to_address_hash: Hash.Address.t() | nil,
uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
v: v(),
- value: Wei.t()
+ value: Wei.t(),
+ revert_reason: String.t()
}
@derive {Poison.Encoder,
@@ -199,6 +203,7 @@ defmodule Explorer.Chain.Transaction do
field(:status, Status)
field(:v, :decimal)
field(:value, Wei)
+ field(:revert_reason, :string)
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated
@@ -419,7 +424,7 @@ defmodule Explorer.Chain.Transaction do
candidates_query
|> Repo.all()
|> Enum.flat_map(fn candidate ->
- case do_decoded_input_data(data, [candidate.abi], hash) do
+ case do_decoded_input_data(data, [candidate.abi], nil, hash) do
{:ok, _, _, _} = decoded -> [decoded]
_ -> []
end
@@ -432,12 +437,18 @@ defmodule Explorer.Chain.Transaction do
{:error, :contract_not_verified, []}
end
- def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do
- do_decoded_input_data(data, abi, hash)
+ def decoded_input_data(%__MODULE__{
+ input: %{bytes: data},
+ to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}},
+ hash: hash
+ }) do
+ do_decoded_input_data(data, abi, address_hash, hash)
end
- defp do_decoded_input_data(data, abi, hash) do
- with {:ok, {selector, values}} <- find_and_decode(abi, data, hash),
+ defp do_decoded_input_data(data, abi, address_hash, hash) do
+ full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
+
+ with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
{:ok, mapping} <- selector_mapping(selector, values, hash),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping),
diff --git a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
index 895d447667..02fd01c4b8 100644
--- a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
+++ b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex
@@ -7,6 +7,7 @@ defmodule Explorer.ChainSpec.GenesisData do
require Logger
+ alias Explorer.ChainSpec.Geth.Importer, as: GethImporter
alias Explorer.ChainSpec.Parity.Importer
alias HTTPoison.Response
@@ -58,11 +59,24 @@ defmodule Explorer.ChainSpec.GenesisData do
path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path]
if path do
+ json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)
+ variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
+
Task.Supervisor.async_nolink(Explorer.GenesisDataTaskSupervisor, fn ->
case fetch_spec(path) do
{:ok, chain_spec} ->
- Importer.import_emission_rewards(chain_spec)
- {:ok, _} = Importer.import_genesis_accounts(chain_spec)
+ case variant do
+ EthereumJSONRPC.Parity ->
+ Importer.import_emission_rewards(chain_spec)
+ {:ok, _} = Importer.import_genesis_accounts(chain_spec)
+
+ EthereumJSONRPC.Geth ->
+ {:ok, _} = GethImporter.import_genesis_accounts(chain_spec)
+
+ _ ->
+ Importer.import_emission_rewards(chain_spec)
+ {:ok, _} = Importer.import_genesis_accounts(chain_spec)
+ end
{:error, reason} ->
Logger.warn(fn -> "Failed to fetch genesis data. #{inspect(reason)}" end)
diff --git a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex
new file mode 100644
index 0000000000..be46375064
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex
@@ -0,0 +1,93 @@
+# credo:disable-for-this-file
+defmodule Explorer.ChainSpec.Geth.Importer do
+ @moduledoc """
+ Imports data from Geth genesis.json.
+ """
+
+ require Logger
+
+ alias EthereumJSONRPC.Blocks
+ alias Explorer.Chain
+ alias Explorer.Chain.Hash.Address, as: AddressHash
+
+ def import_genesis_accounts(chain_spec) do
+ balance_params =
+ chain_spec
+ |> genesis_accounts()
+ |> Stream.map(fn balance_map ->
+ Map.put(balance_map, :block_number, 0)
+ end)
+ |> Enum.to_list()
+
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ {:ok, %Blocks{blocks_params: [%{timestamp: timestamp}]}} =
+ EthereumJSONRPC.fetch_blocks_by_range(1..1, json_rpc_named_arguments)
+
+ day = DateTime.to_date(timestamp)
+
+ balance_daily_params =
+ chain_spec
+ |> genesis_accounts()
+ |> Stream.map(fn balance_map ->
+ Map.put(balance_map, :day, day)
+ end)
+ |> Enum.to_list()
+
+ address_params =
+ balance_params
+ |> Stream.map(fn %{address_hash: hash} = map ->
+ Map.put(map, :hash, hash)
+ end)
+ |> Enum.to_list()
+
+ params = %{
+ address_coin_balances: %{params: balance_params},
+ address_coin_balances_daily: %{params: balance_daily_params},
+ addresses: %{params: address_params}
+ }
+
+ Chain.import(params)
+ end
+
+ def genesis_accounts(chain_spec) do
+ accounts = chain_spec["alloc"]
+
+ if accounts do
+ parse_accounts(accounts)
+ else
+ Logger.warn(fn -> "No accounts are defined in genesis" end)
+
+ []
+ end
+ end
+
+ defp parse_accounts(accounts) do
+ accounts
+ |> Stream.filter(fn {_address, map} ->
+ !is_nil(map["balance"])
+ end)
+ |> Stream.map(fn {address, %{"balance" => value} = params} ->
+ formatted_address = if String.starts_with?(address, "0x"), do: address, else: "0x" <> address
+ {:ok, address_hash} = AddressHash.cast(formatted_address)
+ balance = parse_number(value)
+
+ code = params["code"]
+
+ %{address_hash: address_hash, value: balance, contract_code: code}
+ end)
+ |> Enum.to_list()
+ end
+
+ defp parse_number("0x" <> hex_number) do
+ {number, ""} = Integer.parse(hex_number, 16)
+
+ number
+ end
+
+ defp parse_number(string_number) do
+ {number, ""} = Integer.parse(string_number, 10)
+
+ number
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain_spec/parity/importer.ex b/apps/explorer/lib/explorer/chain_spec/parity/importer.ex
index cdd2da1eed..d4d2ad69df 100644
--- a/apps/explorer/lib/explorer/chain_spec/parity/importer.ex
+++ b/apps/explorer/lib/explorer/chain_spec/parity/importer.ex
@@ -1,6 +1,7 @@
+# credo:disable-for-this-file
defmodule Explorer.ChainSpec.Parity.Importer do
@moduledoc """
- Imports data from parity chain spec.
+ Imports data from Parity/Open Ethereum chain spec.
"""
require Logger
diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex
index 1caf9f7e64..eb7f743ad4 100644
--- a/apps/explorer/lib/explorer/etherscan.ex
+++ b/apps/explorer/lib/explorer/etherscan.ex
@@ -3,7 +3,7 @@ defmodule Explorer.Etherscan do
The etherscan context.
"""
- import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1]
+ import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1, order_by: 3]
alias Explorer.Etherscan.Logs
alias Explorer.{Chain, Repo}
@@ -52,6 +52,22 @@ defmodule Explorer.Etherscan do
end
end
+ @doc """
+ Gets a list of pending transactions for a given `t:Explorer.Chain.Hash.Address.t/0`.
+
+ If `filter_by: `to_address_hash`,
+ `from_address_hash`, and `created_contract_address_hash`.
+
+ """
+ @spec list_pending_transactions(Hash.Address.t()) :: [map()]
+ def list_pending_transactions(
+ %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash,
+ options \\ @default_options
+ ) do
+ merged_options = Map.merge(@default_options, options)
+ list_pending_transactions_query(address_hash, merged_options)
+ end
+
@internal_transaction_fields ~w(
from_address_hash
to_address_hash
@@ -255,8 +271,6 @@ defmodule Explorer.Etherscan do
b in Block,
where: b.miner_hash == ^address_hash,
order_by: [desc: b.number],
- group_by: b.number,
- group_by: b.timestamp,
limit: ^merged_options.page_size,
offset: ^offset(merged_options),
select: %{
@@ -332,8 +346,41 @@ defmodule Explorer.Etherscan do
status
to_address_hash
value
+ revert_reason
+ )a
+
+ @pending_transaction_fields ~w(
+ created_contract_address_hash
+ cumulative_gas_used
+ from_address_hash
+ gas
+ gas_price
+ gas_used
+ hash
+ index
+ input
+ nonce
+ to_address_hash
+ value
+ inserted_at
)a
+ defp list_pending_transactions_query(address_hash, options) do
+ query =
+ from(
+ t in Transaction,
+ limit: ^options.page_size,
+ offset: ^offset(options),
+ select: map(t, ^@pending_transaction_fields)
+ )
+
+ query
+ |> where_address_match(address_hash, options)
+ |> Chain.pending_transactions_query()
+ |> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash)
+ |> Repo.all()
+ end
+
defp list_transactions(address_hash, max_block_number, options) do
query =
from(
diff --git a/apps/explorer/lib/explorer/smart_contract/publisher.ex b/apps/explorer/lib/explorer/smart_contract/publisher.ex
index 28769d2604..24c51b2d24 100644
--- a/apps/explorer/lib/explorer/smart_contract/publisher.ex
+++ b/apps/explorer/lib/explorer/smart_contract/publisher.ex
@@ -37,7 +37,10 @@ defmodule Explorer.SmartContract.Publisher do
publish_smart_contract(address_hash, params_with_external_libaries, abi)
{:error, error} ->
- {:error, unverified_smart_contract(address_hash, params_with_external_libaries, error)}
+ {:error, unverified_smart_contract(address_hash, params_with_external_libaries, error, nil)}
+
+ {:error, error, error_message} ->
+ {:error, unverified_smart_contract(address_hash, params_with_external_libaries, error, error_message)}
end
end
@@ -47,14 +50,15 @@ defmodule Explorer.SmartContract.Publisher do
Chain.create_smart_contract(attrs, attrs.external_libraries)
end
- defp unverified_smart_contract(address_hash, params, error) do
+ defp unverified_smart_contract(address_hash, params, error, error_message) do
attrs = attributes(address_hash, params)
changeset =
SmartContract.invalid_contract_changeset(
%SmartContract{address_hash: address_hash},
attrs,
- error
+ error,
+ error_message
)
%{changeset | action: :insert}
diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex
index 3e25f126e1..897e5595a8 100644
--- a/apps/explorer/lib/explorer/smart_contract/reader.ex
+++ b/apps/explorer/lib/explorer/smart_contract/reader.ex
@@ -112,6 +112,32 @@ defmodule Explorer.SmartContract.Reader do
end)
end
+ @spec query_contract(
+ String.t(),
+ String.t(),
+ term(),
+ functions()
+ ) :: functions_results()
+ def query_contract(contract_address, from, abi, functions) do
+ requests =
+ functions
+ |> Enum.map(fn {function_name, args} ->
+ %{
+ contract_address: contract_address,
+ from: from,
+ function_name: function_name,
+ args: args
+ }
+ end)
+
+ requests
+ |> query_contracts(abi)
+ |> Enum.zip(requests)
+ |> Enum.into(%{}, fn {response, request} ->
+ {request.function_name, response}
+ end)
+ end
+
@doc """
Runs batch of contract functions on given addresses for smart contract with an expected ABI and functions.
@@ -180,7 +206,21 @@ defmodule Explorer.SmartContract.Reader do
end
end
- defp fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
+ def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string) do
+ implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
+
+ case implementation_abi do
+ nil ->
+ []
+
+ _ ->
+ implementation_abi
+ |> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view"))
+ |> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi, contract_address_hash))
+ end
+ end
+
+ def fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
values =
case function do
%{"inputs" => []} ->
@@ -202,26 +242,33 @@ defmodule Explorer.SmartContract.Reader do
@doc """
Fetches the blockchain value of a function that requires arguments.
"""
- @spec query_function(String.t(), %{name: String.t(), args: nil}) :: [%{}]
- def query_function(contract_address_hash, %{name: name, args: nil}) do
- query_function(contract_address_hash, %{name: name, args: []})
+ @spec query_function(String.t(), %{name: String.t(), args: nil}, atom()) :: [%{}]
+ def query_function(contract_address_hash, %{name: name, args: nil}, type) do
+ query_function(contract_address_hash, %{name: name, args: []}, type)
end
- @spec query_function(Hash.t(), %{name: String.t(), args: [term()]}) :: [%{}]
- def query_function(contract_address_hash, %{name: name, args: args}) do
+ @spec query_function(Hash.t(), %{name: String.t(), args: [term()]}, atom()) :: [%{}]
+ def query_function(contract_address_hash, %{name: name, args: args}, type) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
+ final_abi =
+ if type == :proxy do
+ Chain.get_implementation_abi_from_proxy(contract_address_hash, abi)
+ else
+ abi
+ end
+
outputs =
- case abi do
+ case final_abi do
nil ->
nil
_ ->
function =
- abi
+ final_abi
|> Enum.filter(fn function -> function["name"] == name end)
|> List.first()
@@ -229,7 +276,7 @@ defmodule Explorer.SmartContract.Reader do
end
contract_address_hash
- |> query_verified_contract(%{name => normalize_args(args)}, abi)
+ |> query_verified_contract(%{name => normalize_args(args)}, final_abi)
|> link_outputs_and_values(outputs, name)
end
@@ -274,8 +321,21 @@ defmodule Explorer.SmartContract.Reader do
Map.put_new(output, "value", bytes_to_string(value))
end
- defp new_value(%{"type" => "bytes" <> _number} = output, values, index) do
- Map.put_new(output, "value", bytes_to_string(Enum.at(values, index)))
+ defp new_value(%{"type" => "bytes" <> number_rest} = output, values, index) do
+ if String.contains?(number_rest, "[]") do
+ values_array = Enum.at(values, index)
+
+ values_array_formatted =
+ Enum.map(values_array, fn value ->
+ bytes_to_string(value)
+ end)
+
+ values_array_formatted_3 = values_array_formatted ++ values_array_formatted ++ values_array_formatted
+
+ Map.put_new(output, "value", values_array_formatted_3)
+ else
+ Map.put_new(output, "value", bytes_to_string(Enum.at(values, index)))
+ end
end
defp new_value(%{"type" => "bytes"} = output, values, index) do
diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
index 3b83cafed6..4e72e8a5de 100644
--- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
+++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
@@ -116,7 +116,9 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
error ->
error = parse_error(error)
Logger.warn(["There was an error compiling a provided contract: ", inspect(error)])
- {:error, :compilation}
+ {:error, [first_error | _]} = error
+ %{"message" => error_message} = first_error
+ {:error, :compilation, error_message}
end
else
{:error, :compilation}
diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex
index 1b4eb88a3e..6092a39c87 100644
--- a/apps/explorer/lib/explorer/smart_contract/verifier.ex
+++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex
@@ -15,8 +15,10 @@ defmodule Explorer.SmartContract.Verifier do
@metadata_hash_prefix_0_4_23 "a165627a7a72305820"
@metadata_hash_prefix_0_5_10 "a265627a7a72305820"
@metadata_hash_prefix_0_5_11 "a265627a7a72315820"
+ @metadata_hash_prefix_0_5_16 "a365627a7a72315820"
@metadata_hash_prefix_0_6_0 "a264697066735822"
+ @experimental "6c6578706572696d656e74616cf5"
@metadata_hash_common_suffix "64736f6c63"
def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name}
@@ -28,7 +30,11 @@ defmodule Explorer.SmartContract.Verifier do
latest_evm_version = List.last(CodeCompiler.allowed_evm_versions())
evm_version = Map.get(params, "evm_version", latest_evm_version)
- Enum.reduce([evm_version | previous_evm_versions(evm_version)], false, fn version, acc ->
+ all_versions = [evm_version | previous_evm_versions(evm_version)]
+
+ all_versions_extra = all_versions ++ [evm_version]
+
+ Enum.reduce(all_versions_extra, false, fn version, acc ->
case acc do
{:ok, _} = result ->
result
@@ -75,6 +81,10 @@ defmodule Explorer.SmartContract.Verifier do
defp compare_bytecodes({:error, :name}, _, _, _, _, _), do: {:error, :name}
defp compare_bytecodes({:error, _}, _, _, _, _, _), do: {:error, :compilation}
+ defp compare_bytecodes({:error, _, error_message}, _, _, _, _, _) do
+ {:error, :compilation, error_message}
+ end
+
# credo:disable-for-next-line /Complexity/
defp compare_bytecodes(
{:ok, %{"abi" => abi, "bytecode" => bytecode}},
@@ -90,9 +100,15 @@ defmodule Explorer.SmartContract.Verifier do
"compiler_version" => generated_compiler_version
} = extract_bytecode_and_metadata_hash(bytecode)
- "0x" <> blockchain_created_tx_input =
- address_hash
- |> Chain.smart_contract_creation_tx_bytecode()
+ blockchain_created_tx_input =
+ case Chain.smart_contract_creation_tx_bytecode(address_hash) do
+ nil ->
+ bytecode
+
+ blockchain_created_tx_input_with_0x ->
+ "0x" <> blockchain_created_tx_input = blockchain_created_tx_input_with_0x
+ blockchain_created_tx_input
+ end
%{
"metadata_hash" => _metadata_hash,
@@ -214,18 +230,39 @@ defmodule Explorer.SmartContract.Verifier do
"43" <> <> <> "0032" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+ @metadata_hash_prefix_0_5_11 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "43" <> <> <> "0032" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
@metadata_hash_prefix_0_5_11 <>
<> <>
@metadata_hash_common_suffix <>
"7826" <> <> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+ @metadata_hash_prefix_0_5_11 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7826" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
@metadata_hash_prefix_0_5_11 <>
<> <>
@metadata_hash_common_suffix <>
"7827" <> <> <> "0057" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+ @metadata_hash_prefix_0_5_11 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7827" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
@metadata_hash_prefix_0_5_11 <>
<> <>
@metadata_hash_common_suffix <>
@@ -234,6 +271,85 @@ defmodule Explorer.SmartContract.Verifier do
@metadata_hash_prefix_0_5_11 <>
<> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7828" <> <> <> "0058" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_11 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "7829" <> <> <> "0059" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_11 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7829" <> <> <> "0059" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "43" <> <> <> "0032" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "43" <> <> <> "0040" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "7826" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7826" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "7827" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7827" <> <> <> "0057" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "7828" <> <> <> "0058" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @experimental <>
+ @metadata_hash_common_suffix <>
+ "7828" <> <> <> "0058" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @metadata_hash_common_suffix <>
+ "7829" <> <> <> "0059" <> _constructor_arguments ->
+ do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
+
+ @metadata_hash_prefix_0_5_16 <>
+ <> <>
+ @experimental <>
@metadata_hash_common_suffix <>
"7829" <> <> <> "0059" <> _constructor_arguments ->
do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version)
diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
index 8edc758693..ac66b14c6c 100644
--- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
+++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
@@ -9,8 +9,10 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_4_23 "a165627a7a72305820"
@metadata_hash_prefix_0_5_10 "a265627a7a72305820"
@metadata_hash_prefix_0_5_11 "a265627a7a72315820"
+ @metadata_hash_prefix_0_5_16 "a365627a7a72315820"
@metadata_hash_prefix_0_6_0 "a264697066735822"
+ @experimental "6c6578706572696d656e74616cf5"
@metadata_hash_common_suffix "64736f6c63"
def verify(address_hash, contract_code, arguments_data, contract_source_code, contract_name) do
@@ -120,6 +122,18 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_5_11
)
+ @metadata_hash_prefix_0_5_11 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_11
+ )
+
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
@@ -131,6 +145,18 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_5_11
)
+ @metadata_hash_prefix_0_5_11 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_11
+ )
+
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
@@ -142,6 +168,18 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_5_11
)
+ @metadata_hash_prefix_0_5_11 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_11
+ )
+
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
@@ -153,6 +191,18 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_5_11
)
+ @metadata_hash_prefix_0_5_11 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_11
+ )
+
@metadata_hash_prefix_0_5_11 <>
<<_::binary-size(64)>> <>
@metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
@@ -164,6 +214,134 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@metadata_hash_prefix_0_5_11
)
+ @metadata_hash_prefix_0_5_11 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_11
+ )
+
+ # ABIEncoder V2
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @metadata_hash_prefix_0_5_16
+ )
+
+ @metadata_hash_prefix_0_5_16 <>
+ <<_::binary-size(64)>> <>
+ @experimental <>
+ @metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments ->
+ split_constructor_arguments_and_extract_check_func(
+ constructor_arguments,
+ check_func,
+ contract_source_code,
+ contract_name,
+ @experimental <> @metadata_hash_prefix_0_5_16
+ )
+
# Solidity >= 0.6.0 https://github.com/ethereum/solidity/blob/develop/Changelog.md#060-2019-12-17
# https://github.com/ethereum/solidity/blob/26b700771e9cc9c956f0503a05de69a1be427963/docs/metadata.rst#encoding-of-the-metadata-hash-in-the-bytecode
# IPFS is used instead of Swarm
diff --git a/apps/explorer/lib/explorer/smart_contract/writer.ex b/apps/explorer/lib/explorer/smart_contract/writer.ex
new file mode 100644
index 0000000000..872742fe73
--- /dev/null
+++ b/apps/explorer/lib/explorer/smart_contract/writer.ex
@@ -0,0 +1,57 @@
+defmodule Explorer.SmartContract.Writer do
+ @moduledoc """
+ Generates smart-contract transactions
+ """
+
+ alias Explorer.Chain
+
+ @spec write_functions(Hash.t()) :: [%{}]
+ def write_functions(contract_address_hash) do
+ abi =
+ contract_address_hash
+ |> Chain.address_hash_to_smart_contract()
+ |> Map.get(:abi)
+
+ case abi do
+ nil ->
+ []
+
+ _ ->
+ abi
+ |> filter_write_functions()
+ end
+ end
+
+ @spec write_functions_proxy(Hash.t()) :: [%{}]
+ def write_functions_proxy(implementation_address_hash_string) do
+ implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
+
+ case implementation_abi do
+ nil ->
+ []
+
+ _ ->
+ implementation_abi
+ |> filter_write_functions()
+ end
+ end
+
+ def write_function?(function) do
+ !event?(function) && !constructor?(function) &&
+ (payable?(function) || nonpayable?(function))
+ end
+
+ defp filter_write_functions(abi) do
+ abi
+ |> Enum.filter(&write_function?(&1))
+ end
+
+ defp event?(function), do: function["type"] == "event"
+ defp constructor?(function), do: function["type"] == "constructor"
+ defp payable?(function), do: function["stateMutability"] == "payable" || function["payable"]
+
+ defp nonpayable?(function),
+ do:
+ function["stateMutability"] == "nonpayable" ||
+ (!function["payable"] && !function["constant"] && !function["stateMutability"])
+end
diff --git a/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs b/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs
new file mode 100644
index 0000000000..6f0ab6320e
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.AlterTransactionsAddErrorReason do
+ use Ecto.Migration
+
+ def change do
+ alter table(:transactions) do
+ add(:revert_reason, :text)
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs
index 3619476f6c..e0f0fa7c14 100644
--- a/apps/explorer/test/explorer/chain/log_test.exs
+++ b/apps/explorer/test/explorer/chain/log_test.exs
@@ -58,29 +58,29 @@ defmodule Explorer.Chain.LogTest do
end
test "that a contract call transaction that has a verified contract returns the decoded input data" do
- smart_contract =
- insert(:smart_contract,
- abi: [
- %{
- "anonymous" => false,
- "inputs" => [
- %{"indexed" => true, "name" => "_from_human", "type" => "string"},
- %{"indexed" => false, "name" => "_number", "type" => "uint256"},
- %{"indexed" => true, "name" => "_belly", "type" => "bool"}
- ],
- "name" => "WantsPets",
- "type" => "event"
- }
- ]
- )
+ to_address = insert(:address, contract_code: "0x")
+
+ insert(:smart_contract,
+ abi: [
+ %{
+ "anonymous" => false,
+ "inputs" => [
+ %{"indexed" => true, "name" => "_from_human", "type" => "string"},
+ %{"indexed" => false, "name" => "_number", "type" => "uint256"},
+ %{"indexed" => true, "name" => "_belly", "type" => "bool"}
+ ],
+ "name" => "WantsPets",
+ "type" => "event"
+ }
+ ],
+ address_hash: to_address.hash
+ )
topic1 = "0x" <> Base.encode16(:keccakf1600.hash(:sha3_256, "WantsPets(string,uint256,bool)"), case: :lower)
topic2 = "0x" <> Base.encode16(:keccakf1600.hash(:sha3_256, "bob"), case: :lower)
topic3 = "0x0000000000000000000000000000000000000000000000000000000000000001"
data = "0x0000000000000000000000000000000000000000000000000000000000000000"
- to_address = insert(:address, smart_contract: smart_contract)
-
transaction =
:transaction_to_verified_contract
|> insert(to_address: to_address)
@@ -88,6 +88,7 @@ defmodule Explorer.Chain.LogTest do
log =
insert(:log,
+ address: to_address,
transaction: transaction,
first_topic: topic1,
second_topic: topic2,
diff --git a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
new file mode 100644
index 0000000000..36eebf24a7
--- /dev/null
+++ b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs
@@ -0,0 +1,145 @@
+defmodule Explorer.ChainSpec.Geth.ImporterTest do
+ use Explorer.DataCase
+
+ import Mox
+ import EthereumJSONRPC, only: [integer_to_quantity: 1]
+
+ alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily}
+ alias Explorer.Chain.{Address, Hash}
+ alias Explorer.ChainSpec.Geth.Importer
+ alias Explorer.Repo
+
+ setup :set_mox_global
+
+ @genesis "#{File.cwd!()}/test/support/fixture/chain_spec/qdai_genesis.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ describe "genesis_accounts/1" do
+ test "parses coin balance and contract code" do
+ coin_balances = Importer.genesis_accounts(@genesis)
+
+ assert Enum.count(coin_balances) == 3
+
+ assert %{
+ address_hash: %Hash{
+ byte_count: 20,
+ bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 254>>
+ },
+ value: 0,
+ contract_code:
+ "0x6080604052600436106100cf5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630f3a053381146100d457806310f2ee7c146101075780632ee57f8d1461011c57806330f6eb16146101665780633d84b8c11461018a5780634476d66a146101ab578063553a5c85146101c3578063899ca7fc146101d8578063aa9fa27414610225578063b4a523e81461024b578063db456f771461026c578063e73b9e2f146102a0578063efdc4d01146102c1578063f91c2898146102d6575b600080fd5b3480156100e057600080fd5b506100f5600160a060020a036004351661039b565b60408051918252519081900360200190f35b34801561011357600080fd5b506100f5610464565b34801561012857600080fd5b50610131610469565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561017257600080fd5b506100f5600160a060020a036004351660243561049e565b34801561019657600080fd5b506100f5600160a060020a0360043516610570565b3480156101b757600080fd5b506100f56004356105f7565b3480156101cf57600080fd5b506100f561066f565b3480156101e457600080fd5b506101ed6106bd565b6040518082602080838360005b838110156102125781810151838201526020016101fa565b5050505090500191505060405180910390f35b34801561023157600080fd5b50610249600435600160a060020a03602435166106eb565b005b34801561025757600080fd5b506100f5600160a060020a03600435166107bd565b34801561027857600080fd5b50610284600435610844565b60408051600160a060020a039092168252519081900360200190f35b3480156102ac57600080fd5b506100f5600160a060020a0360043516610908565b3480156102cd57600080fd5b506100f561098f565b3480156102e257600080fd5b5061030260246004803582810192908201359181359182019101356109dd565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561034657818101518382015260200161032e565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561038557818101518382015260200161036d565b5050505090500194505050505060405180910390f35b604080517f0f09dbb26898a3af738d25c5fff308337ac8f2b0acbbaf209b373fb1389bcf2f602080830191909152606060020a600160a060020a038516028284015282516034818403018152605490920192839052815160009384938493909282918401908083835b602083106104235780518252601f199092019160209182019101610404565b51815160209384036101000a600019018019909216911617905260408051929094018290039091208652850195909552929092016000205495945050505050565b600181565b604080517f626c6f636b5265776172640000000000000000000000000000000000000000008152905190819003600b01902090565b604080517f24ae442c1f305c4f1294bf2dddd491a64250b2818b446706e9a74aeaaaf6f419602080830191909152606060020a600160a060020a0386160282840152605480830185905283518084039091018152607490920192839052815160009384938493909282918401908083835b6020831061052e5780518252601f19909201916020918201910161050f565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120865285019590955292909201600020549695505050505050565b604080517f0fd3be07b1332be84678873bf53feb10604cd09244fb4bb9154e03e00709b9e7602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b604080517f3840e646f7ce9b3210f5440e2dbd6b36451169bfdac65ef00a161729eded81bd60208083019190915281830184905282518083038401815260609092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b7f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2560009081526020527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c9515490565b6106c56112f3565b50604080516020810190915273bf3d6f830ce263cae987193982192cd990442b53815290565b60006106f633610baf565b151561070157600080fd5b82151561070d57600080fd5b600160a060020a038216151561072257600080fd5b61072b8261039b565b905080151561073d5761073d82610c1a565b610756610750828563ffffffff610d5716565b83610d6a565b6107786107728461076633610908565b9063ffffffff610d5716565b33610e34565b6040805184815290513391600160a060020a038516917f3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a9181900360200190a3505050565b604080517f12e71282a577e2b463da2c18bc96b6122db29bcef9065ed5a7f0f9316c11c08e602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b604080517fa47da669ec9f3749fbb12db00588b5fa6b5bbd24da81eb6cab44261334c21c1760208083019190915281830184905282518083038401815260609092019283905281516000936002938593909282918401908083835b602083106108be5780518252601f19909201916020918201910161089f565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912086528501959095529290920160002054600160a060020a031695945050505050565b604080517fa7f48dc57b1a051b1732e5ed136bbfd33bb5aa418e3e3498901320529e785461602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b7f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d4460009081526020527f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f5490565b60608060008180828080808033156109f457600080fd5b8c156109ff57600080fd5b8a15610a0a57600080fd5b610a1261098f565b975087604051908082528060200260200182016040528015610a3e578160200160208202803883390190505b50965087604051908082528060200260200182016040528015610a6b578160200160208202803883390190505b509550600094505b87851015610ae757610a8485610844565b9350610a8f8461039b565b9250610a9c600085610d6a565b838786815181101515610aab57fe5b600160a060020a0390921660209283029091019091015285518390879087908110610ad257fe5b60209081029091010152600190940193610a73565b600094505b87851015610b3757610b2c8686815181101515610b0557fe5b906020019060200201518887815181101515610b1d57fe5b90602001906020020151610ebb565b600190940193610aec565b600094505b6001851015610b9357610b4d6106bd565b8560018110610b5857fe5b60200201519150610b6882610908565b90506000811115610b8857610b7e600083610e34565b610b8881836111c4565b600190940193610b3c565b610b9b6112a4565b50949c939b50929950505050505050505050565b6000610bb96112f3565b6000610bc36106bd565b9150600090505b6001811015610c0e57818160018110610bdf57fe5b6020020151600160a060020a031684600160a060020a03161415610c065760019250610c13565b600101610bca565b600092505b5050919050565b6000610c2461098f565b604080517fa47da669ec9f3749fbb12db00588b5fa6b5bbd24da81eb6cab44261334c21c176020808301919091528183018490528251808303840181526060909201928390528151939450859360029360009392909182918401908083835b60208310610ca25780518252601f199092019160209182019101610c83565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912086528581019690965250929092016000908120805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03969096169590951790945550507f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d448252526001017f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f5550565b81810182811015610d6457fe5b92915050565b604080517f0f09dbb26898a3af738d25c5fff308337ac8f2b0acbbaf209b373fb1389bcf2f602080830191909152606060020a600160a060020a038516028284015282516034818403018152605490920192839052815185936000938493909282918401908083835b60208310610df25780518252601f199092019160209182019101610dd3565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120865285019590955292909201600020939093555050505050565b604080517fa7f48dc57b1a051b1732e5ed136bbfd33bb5aa418e3e3498901320529e785461602080830191909152606060020a600160a060020a0385160282840152825160348184030181526054909201928390528151859360009384939092829184019080838360208310610df25780518252601f199092019160209182019101610dd3565b604080517f24ae442c1f305c4f1294bf2dddd491a64250b2818b446706e9a74aeaaaf6f419602080830191909152606060020a600160a060020a038516028284015243605480840191909152835180840390910181526074909201928390528151600093918291908401908083835b60208310610f495780518252601f199092019160209182019101610f2a565b51815160209384036101000a60001901801990921691161790526040805192909401829003822060008181528083528590208a90557f0fd3be07b1332be84678873bf53feb10604cd09244fb4bb9154e03e00709b9e783830152600160a060020a038916606060020a02838601528451808403603401815260549093019485905282519097509195509293508392850191508083835b60208310610ffe5780518252601f199092019160209182019101610fdf565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600081815291829052929020549194506110469350909150859050610d57565b600082815260208181526040918290209290925580517f3840e646f7ce9b3210f5440e2dbd6b36451169bfdac65ef00a161729eded81bd818401524381830152815180820383018152606090910191829052805190928291908401908083835b602083106110c55780518252601f1990920191602091820191016110a6565b51815160209384036101000a600019018019909216911617905260408051929094018290039091206000818152918290529290205491945061110d9350909150859050610d57565b6000828152602081905260408120919091557f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2590527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c95154611174908463ffffffff610d5716565b7f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2560009081526020527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c95155505050565b604080517f12e71282a577e2b463da2c18bc96b6122db29bcef9065ed5a7f0f9316c11c08e602080830191909152606060020a600160a060020a0385160282840152825160348184030181526054909201928390528151600093918291908401908083835b602083106112485780518252601f199092019160209182019101611229565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600081815291829052929020549194506112909350909150859050610d57565b600091825260208290526040909120555050565b7f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d44600090815260208190527f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f55565b60206040519081016040528060019060208202803883395091929150505600a165627a7a7230582053b0e89d867fc0c586739f4911c11be5aaee046320d1dff0da51c1b04404b4a00029"
+ } ==
+ List.first(coin_balances)
+ end
+ end
+
+ describe "import_genesis_accounts/1" do
+ test "imports accounts" do
+ block_quantity = integer_to_quantity(1)
+ res = eth_block_number_fake_response(block_quantity)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
+ ],
+ _ ->
+ {:ok, [res]}
+ end)
+
+ {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@genesis)
+
+ assert Enum.count(address_coin_balances) == 3
+ assert CoinBalance |> Repo.all() |> Enum.count() == 3
+ assert CoinBalanceDaily |> Repo.all() |> Enum.count() == 3
+ assert Address |> Repo.all() |> Enum.count() == 3
+ end
+
+ test "imports contract code" do
+ block_quantity = integer_to_quantity(1)
+ res = eth_block_number_fake_response(block_quantity)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
+ ],
+ [] ->
+ {:ok, [res]}
+ end)
+
+ code =
+ "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806391ad27b41161008c57806398d5fdca1161006657806398d5fdca14610262578063a97e5c9314610280578063df5dd1a5146102dc578063eebd48b014610320576100cf565b806391ad27b4146101e457806391b7f5ed14610202578063955d14cd14610244576100cf565b80630aa6f2fe146100d457806320ba81ee1461011657806322a90082146101345780634c2c987c14610176578063764cbcd1146101985780637837efdc146101da575b600080fd5b610100600480360360208110156100ea57600080fd5b8101908080359060200190929190505050610353565b6040518082815260200191505060405180910390f35b61011e6103c4565b6040518082815260200191505060405180910390f35b6101606004803603602081101561014a57600080fd5b81019080803590602001909291905050506103ce565b6040518082815260200191505060405180910390f35b61017e61043f565b604051808215151515815260200191505060405180910390f35b6101c4600480360360208110156101ae57600080fd5b8101908080359060200190929190505050610456565b6040518082815260200191505060405180910390f35b6101e26104c7565b005b6101ec6104d2565b6040518082815260200191505060405180910390f35b61022e6004803603602081101561021857600080fd5b81019080803590602001909291905050506104dc565b6040518082815260200191505060405180910390f35b61024c6106a2565b6040518082815260200191505060405180910390f35b61026a6106ac565b6040518082815260200191505060405180910390f35b6102c26004803603602081101561029657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506106b6565b604051808215151515815260200191505060405180910390f35b61031e600480360360208110156102f257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506106d3565b005b61032861073d565b6040518085815260200184815260200183815260200182815260200194505050505060405180910390f35b600061035e336106b6565b6103b3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b816004819055506004549050919050565b6000600454905090565b60006103d9336106b6565b61042e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b816003819055506003549050919050565b6000600560009054906101000a900460ff16905090565b6000610461336106b6565b6104b6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b816002819055506002549050919050565b6104d033610771565b565b6000600354905090565b60006104e7336106b6565b61053c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b600082116105b2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f7072696365203c3d30000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6105ba6104d2565b6105c26106a2565b01421015610638576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f54494d455f4c4f434b5f494e434f4d504c45544500000000000000000000000081525060200191505060405180910390fd5b610641826107cb565b5061064b42610456565b503373ffffffffffffffffffffffffffffffffffffffff167f95dce27040c59c8b1c445b284f81a3aaae6eecd7d08d5c7684faee64cdb514a1836040518082815260200191505060405180910390a2819050919050565b6000600254905090565b6000600154905090565b60006106cc82600061083c90919063ffffffff16565b9050919050565b6106dc336106b6565b610731576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b61073a8161091a565b50565b60008060008061074b6106ac565b6107536104d2565b61075b6103c4565b6107636106a2565b935093509350935090919293565b61078581600061097390919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167f9c8e7d83025bef8a04c664b2f753f64b8814bdb7e27291d7e50935f18cc3c71260405160405180910390a250565b60006107d6336106b6565b61082b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526030815260200180610b4f6030913960400191505060405180910390fd5b816001819055506001549050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156108c3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610b2d6022913960400191505060405180910390fd5b8260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b61092e816000610a3090919063ffffffff16565b8073ffffffffffffffffffffffffffffffffffffffff167e47706786c922d17b39285dc59d696bafea72c0b003d3841ae1202076f4c2e460405160405180910390a250565b61097d828261083c565b6109d2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180610b0c6021913960400191505060405180910390fd5b60008260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b610a3a828261083c565b15610aad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650081525060200191505060405180910390fd5b60018260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550505056fe526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c65526f6c65733a206163636f756e7420697320746865207a65726f20616464726573734f7261636c65526f6c653a2063616c6c657220646f6573206e6f74206861766520746865204f7261636c6520726f6c65a265627a7a72315820df30730da57a5061c487e0b37e84e80308fa443e2e80ee9117a13fa8149caf4164736f6c634300050b0032"
+
+ chain_spec = %{
+ "alloc" => %{
+ "0xcd59f3dde77e09940befb6ee58031965cae7a336" => %{
+ "balance" => "0x21e19e0c9bab2400000",
+ "code" => code
+ }
+ }
+ }
+
+ {:ok, _} = Importer.import_genesis_accounts(chain_spec)
+
+ address = Address |> Repo.one()
+
+ assert to_string(address.contract_code) == code
+ end
+
+ test "imports coin balances without 0x" do
+ block_quantity = integer_to_quantity(1)
+ res = eth_block_number_fake_response(block_quantity)
+
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", params: ["0x1", true]}
+ ],
+ [] ->
+ {:ok, [res]}
+ end)
+
+ {:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_accounts(@genesis)
+
+ assert Enum.count(address_coin_balances) == 3
+ assert CoinBalance |> Repo.all() |> Enum.count() == 3
+ assert CoinBalanceDaily |> Repo.all() |> Enum.count() == 3
+ assert Address |> Repo.all() |> Enum.count() == 3
+ end
+ end
+
+ defp eth_block_number_fake_response(block_quantity) do
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ result: %{
+ "author" => "0x0000000000000000000000000000000000000000",
+ "difficulty" => "0x20000",
+ "extraData" => "0x",
+ "gasLimit" => "0x663be0",
+ "gasUsed" => "0x0",
+ "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
+ "logsBloom" =>
+ "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "miner" => "0x0000000000000000000000000000000000000000",
+ "number" => block_quantity,
+ "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "sealFields" => [
+ "0x80",
+ "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "signature" =>
+ "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "size" => "0x215",
+ "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
+ "step" => "0",
+ "timestamp" => "0x0",
+ "totalDifficulty" => "0x20000",
+ "transactions" => [],
+ "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+ "uncles" => []
+ }
+ }
+ end
+end
diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs
index 76774460c1..dc9b9f59ba 100644
--- a/apps/explorer/test/explorer/chain_test.exs
+++ b/apps/explorer/test/explorer/chain_test.exs
@@ -5165,4 +5165,278 @@ defmodule Explorer.ChainTest do
}
end
end
+
+ describe "transaction_to_revert_reason/1" do
+ test "returns correct revert_reason from DB" do
+ transaction = insert(:transaction, revert_reason: "No credit of that type")
+ assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
+ end
+
+ test "returns correct revert_reason from the archive node" do
+ transaction =
+ insert(:transaction,
+ gas: 27319,
+ gas_price: "0x1b31d2900",
+ value: "0x86b3",
+ input: %Explorer.Chain.Data{bytes: <<1>>}
+ )
+ |> with_block(insert(:block, number: 1))
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
+ end
+ )
+
+ assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
+ end
+ end
+
+ describe "proxy contracts features" do
+ @proxy_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "bool", "name" => ""}],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "address", "name" => "newImplementation"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "renounceOwnership",
+ "inputs" => [],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getOwner",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getProxyStorage",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferOwnership",
+ "inputs" => [%{"type" => "address", "name" => "_newOwner"}],
+ "constant" => false
+ },
+ %{
+ "type" => "constructor",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "inputs" => [
+ %{"type" => "address", "name" => "_proxyStorage"},
+ %{"type" => "address", "name" => "_implementationAddress"}
+ ]
+ },
+ %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
+ %{
+ "type" => "event",
+ "name" => "Upgraded",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version", "indexed" => false},
+ %{"type" => "address", "name" => "implementation", "indexed" => true}
+ ],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipRenounced",
+ "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipTransferred",
+ "inputs" => [
+ %{"type" => "address", "name" => "previousOwner", "indexed" => true},
+ %{"type" => "address", "name" => "newOwner", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ @implementation_abi [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == []
+ end
+
+ test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
+ end
+
+ test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi
+ end
+
+ test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+
+ implementation_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]}
+ end
+ )
+
+ combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi)
+
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+ assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == []
+ end
+
+ test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+
+ implementation_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]}
+ end
+ )
+
+ implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi)
+
+ assert implementation_abi == @implementation_abi
+ end
+
+ test "get_implementation_abi/1 returns empty [] abi if implmentation address is null" do
+ assert Chain.get_implementation_abi(nil) == []
+ end
+
+ test "get_implementation_abi/1 returns [] if implementation is not verified" do
+ implementation_contract_address = insert(:contract_address)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == []
+ end
+
+ test "get_implementation_abi/1 returns implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+
+ implementation_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string)
+
+ assert implementation_abi == @implementation_abi
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs
index 4761990b82..d3cc73b554 100644
--- a/apps/explorer/test/explorer/etherscan_test.exs
+++ b/apps/explorer/test/explorer/etherscan_test.exs
@@ -463,6 +463,156 @@ defmodule Explorer.EtherscanTest do
end
end
+ describe "list_pending_transactions/2" do
+ test "with empty db" do
+ address = build(:address)
+
+ assert Etherscan.list_pending_transactions(address.hash) == []
+ end
+
+ test "with from address" do
+ address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(from_address: address)
+
+ [found_transaction] = Etherscan.list_pending_transactions(address.hash)
+
+ assert transaction.hash == found_transaction.hash
+ end
+
+ test "with to address" do
+ address = insert(:address)
+
+ transaction =
+ :transaction
+ |> insert(to_address: address)
+
+ [found_transaction] = Etherscan.list_pending_transactions(address.hash)
+
+ assert transaction.hash == found_transaction.hash
+ end
+
+ test "with same to and from address" do
+ address = insert(:address)
+
+ _transaction =
+ :transaction
+ |> insert(from_address: address, to_address: address)
+
+ found_transactions = Etherscan.list_pending_transactions(address.hash)
+
+ assert length(found_transactions) == 1
+ end
+
+ test "with address with 0 transactions" do
+ address1 = insert(:address)
+ address2 = insert(:address)
+
+ :transaction
+ |> insert(from_address: address2)
+
+ assert Etherscan.list_pending_transactions(address1.hash) == []
+ end
+
+ test "with address with multiple transactions" do
+ address1 = insert(:address)
+ address2 = insert(:address)
+
+ 3
+ |> insert_list(:transaction, from_address: address1)
+
+ :transaction
+ |> insert(from_address: address2)
+
+ found_transactions = Etherscan.list_pending_transactions(address1.hash)
+
+ assert length(found_transactions) == 3
+
+ for found_transaction <- found_transactions do
+ assert found_transaction.from_address_hash == address1.hash
+ end
+ end
+
+ test "orders transactions by inserted_at, in descending order" do
+ address = insert(:address)
+
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ options = %{order_by_direction: :desc}
+
+ found_transactions = Etherscan.list_pending_transactions(address.hash, options)
+
+ inserted_at_order = Enum.map(found_transactions, & &1.inserted_at)
+
+ assert inserted_at_order == Enum.sort(inserted_at_order, &(&1 >= &2))
+ end
+
+ test "with page_size and page_number options" do
+ address = insert(:address)
+
+ transactions_1 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ transactions_2 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ transactions_3 =
+ 2
+ |> insert_list(:transaction, from_address: address)
+
+ options = %{page_number: 1, page_size: 2}
+
+ page1_transactions = Etherscan.list_pending_transactions(address.hash, options)
+
+ page1_hashes = Enum.map(page1_transactions, & &1.hash)
+
+ assert length(page1_transactions) == 2
+
+ for transaction <- transactions_3 do
+ assert transaction.hash in page1_hashes
+ end
+
+ options = %{page_number: 2, page_size: 2}
+
+ page2_transactions = Etherscan.list_pending_transactions(address.hash, options)
+
+ page2_hashes = Enum.map(page2_transactions, & &1.hash)
+
+ assert length(page2_transactions) == 2
+
+ for transaction <- transactions_2 do
+ assert transaction.hash in page2_hashes
+ end
+
+ options = %{page_number: 3, page_size: 2}
+
+ page3_transactions = Etherscan.list_pending_transactions(address.hash, options)
+
+ page3_hashes = Enum.map(page3_transactions, & &1.hash)
+
+ assert length(page3_transactions) == 2
+
+ for transaction <- transactions_1 do
+ assert transaction.hash in page3_hashes
+ end
+
+ options = %{page_number: 4, page_size: 2}
+
+ assert Etherscan.list_pending_transactions(address.hash, options) == []
+ end
+ end
+
describe "list_internal_transactions/1 with transaction hash" do
test "with empty db" do
transaction = build(:transaction)
diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs
index 82f0a54385..17ede61237 100644
--- a/apps/explorer/test/explorer/smart_contract/reader_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs
@@ -169,7 +169,89 @@ defmodule Explorer.SmartContract.ReaderTest do
end
end
- describe "query_function/2" do
+ describe "read_only_functions_proxy/1" do
+ test "fetches the smart contract proxy read only functions with the blockchain value" do
+ proxy_smart_contract =
+ insert(:smart_contract,
+ abi: [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{
+ "type" => "address",
+ "name" => ""
+ }
+ ],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ }
+ ]
+ )
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: [
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "with_arguments",
+ "outputs" => [%{"name" => "", "type" => "bool"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ blockchain_get_function_mock()
+
+ response =
+ Reader.read_only_functions_proxy(
+ proxy_smart_contract.address_hash,
+ "0x" <> implementation_contract_address_hash_string
+ )
+
+ assert [
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}],
+ "payable" => _,
+ "stateMutability" => _,
+ "type" => _
+ },
+ %{
+ "constant" => true,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "with_arguments",
+ "outputs" => [%{"name" => "", "type" => "bool", "value" => ""}],
+ "payable" => _,
+ "stateMutability" => _,
+ "type" => _
+ }
+ ] = response
+ end
+ end
+
+ describe "query_function/3" do
test "given the arguments, fetches the function value from the blockchain" do
smart_contract = insert(:smart_contract)
@@ -181,7 +263,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"type" => "uint256",
"value" => 0
}
- ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: []})
+ ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: []}, :regular)
end
test "nil arguments is treated as []" do
@@ -195,7 +277,7 @@ defmodule Explorer.SmartContract.ReaderTest do
"type" => "uint256",
"value" => 0
}
- ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: nil})
+ ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: nil}, :regular)
end
end
diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
index 270684c8d1..8d092d66bf 100644
--- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
@@ -166,7 +166,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
}
"""
- version = "v0.1.3-nightly.2015.9.25+commit.4457170"
+ version = "v0.1.3+commit.028f561d"
response = CodeCompiler.run(name: name, compiler_version: version, code: code, optimize: optimize)
diff --git a/apps/explorer/test/explorer/smart_contract/writer_test.exs b/apps/explorer/test/explorer/smart_contract/writer_test.exs
new file mode 100644
index 0000000000..00037b7149
--- /dev/null
+++ b/apps/explorer/test/explorer/smart_contract/writer_test.exs
@@ -0,0 +1,329 @@
+defmodule Explorer.SmartContract.WriterTest do
+ use EthereumJSONRPC.Case
+ use Explorer.DataCase
+
+ import Mox
+
+ alias Explorer.SmartContract.Writer
+
+ @abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "upgradeabilityOwner",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "payable" => true,
+ "outputs" => [],
+ "name" => "upgradeToAndCall",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"},
+ %{"type" => "bytes", "name" => "data"}
+ ],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferProxyOwnership",
+ "inputs" => [%{"type" => "address", "name" => "newOwner"}],
+ "constant" => false
+ },
+ %{"type" => "fallback", "stateMutability" => "payable", "payable" => true},
+ %{
+ "type" => "event",
+ "name" => "ProxyOwnershipTransferred",
+ "inputs" => [
+ %{"type" => "address", "name" => "previousOwner", "indexed" => false},
+ %{"type" => "address", "name" => "newOwner", "indexed" => false}
+ ],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "Upgraded",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version", "indexed" => false},
+ %{"type" => "address", "name" => "implementation", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ @implementation_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "extraReceiverAmount",
+ "inputs" => [%{"type" => "address", "name" => "_receiver"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "bridgesAllowedLength",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "pure",
+ "payable" => false,
+ "outputs" => [%{"type" => "bytes4", "name" => ""}],
+ "name" => "blockRewardContractId",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "mintedForAccountInBlock",
+ "inputs" => [%{"type" => "address", "name" => "_account"}, %{"type" => "uint256", "name" => "_blockNumber"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "mintedForAccount",
+ "inputs" => [%{"type" => "address", "name" => "_account"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "mintedInBlock",
+ "inputs" => [%{"type" => "uint256", "name" => "_blockNumber"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "mintedTotally",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "pure",
+ "payable" => false,
+ "outputs" => [%{"type" => "address[1]", "name" => ""}],
+ "name" => "bridgesAllowed",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "addExtraReceiver",
+ "inputs" => [%{"type" => "uint256", "name" => "_amount"}, %{"type" => "address", "name" => "_receiver"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "mintedTotallyByBridge",
+ "inputs" => [%{"type" => "address", "name" => "_bridge"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "extraReceiverByIndex",
+ "inputs" => [%{"type" => "uint256", "name" => "_index"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "bridgeAmount",
+ "inputs" => [%{"type" => "address", "name" => "_bridge"}],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "extraReceiversLength",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
+ "name" => "reward",
+ "inputs" => [%{"type" => "address[]", "name" => "benefactors"}, %{"type" => "uint16[]", "name" => "kind"}],
+ "constant" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "AddedReceiver",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "amount", "indexed" => false},
+ %{"type" => "address", "name" => "receiver", "indexed" => true},
+ %{"type" => "address", "name" => "bridge", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ doctest Explorer.SmartContract.Writer
+
+ setup :verify_on_exit!
+
+ describe "write_functions/1" do
+ test "fetches the smart contract write functions" do
+ smart_contract =
+ insert(
+ :smart_contract,
+ abi: @abi
+ )
+
+ response = Writer.write_functions(smart_contract.address_hash)
+
+ assert [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "upgradeTo",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"}
+ ],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "payable",
+ "payable" => true,
+ "outputs" => [],
+ "name" => "upgradeToAndCall",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version"},
+ %{"type" => "address", "name" => "implementation"},
+ %{"type" => "bytes", "name" => "data"}
+ ],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferProxyOwnership",
+ "inputs" => [%{"type" => "address", "name" => "newOwner"}],
+ "constant" => false
+ },
+ %{"type" => "fallback", "stateMutability" => "payable", "payable" => true}
+ ] = response
+ end
+ end
+
+ describe "write_functions_proxy/1" do
+ test "fetches the smart contract proxy write functions" do
+ proxy_smart_contract =
+ insert(:smart_contract,
+ abi: @abi
+ )
+
+ implementation_contract_address = insert(:contract_address)
+
+ insert(:smart_contract,
+ address_hash: implementation_contract_address.hash,
+ abi: @implementation_abi
+ )
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ response = Writer.write_functions_proxy("0x" <> implementation_contract_address_hash_string)
+
+ assert [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "addExtraReceiver",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "_amount"},
+ %{"type" => "address", "name" => "_receiver"}
+ ],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
+ "name" => "reward",
+ "inputs" => [
+ %{"type" => "address[]", "name" => "benefactors"},
+ %{"type" => "uint16[]", "name" => "kind"}
+ ],
+ "constant" => false
+ }
+ ] = response
+ end
+ end
+end
diff --git a/apps/explorer/test/support/fixture/chain_spec/qdai_genesis.json b/apps/explorer/test/support/fixture/chain_spec/qdai_genesis.json
new file mode 100644
index 0000000000..893557611b
--- /dev/null
+++ b/apps/explorer/test/support/fixture/chain_spec/qdai_genesis.json
@@ -0,0 +1,53 @@
+{
+ "config": {
+ "chainId": 181,
+ "eip150Block": 0,
+ "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "homesteadBlock": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "istanbul": {
+ "epoch": 30000,
+ "policy": 0,
+ "ceil2Nby3Block": 0
+ },
+ "maxCodeSizeConfig": [
+ {
+ "block": 0,
+ "size": 35
+ }
+ ],
+ "isQuorum": true,
+ "txnSizeLimit": 64,
+ "blockRewardConfig": {
+ "contract": "0x000000000000000000000000000000000000ffff"
+ }
+ },
+ "nonce": "0x0",
+ "timestamp": "0x5ef3b16e",
+ "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f85ad5944cca306665b4249e7964c7dbfb136627eccc0268b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0",
+ "gasLimit": "0xbebc20",
+ "difficulty": "0x1",
+ "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
+ "coinbase": "0x0000000000000000000000000000000000000000",
+ "alloc": {
+ "4cca306665b4249e7964c7dbfb136627eccc0268": {
+ "balance": "0x0"
+ },
+ "000000000000000000000000000000000000fffe": {
+ "code": "0x6080604052600436106100cf5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630f3a053381146100d457806310f2ee7c146101075780632ee57f8d1461011c57806330f6eb16146101665780633d84b8c11461018a5780634476d66a146101ab578063553a5c85146101c3578063899ca7fc146101d8578063aa9fa27414610225578063b4a523e81461024b578063db456f771461026c578063e73b9e2f146102a0578063efdc4d01146102c1578063f91c2898146102d6575b600080fd5b3480156100e057600080fd5b506100f5600160a060020a036004351661039b565b60408051918252519081900360200190f35b34801561011357600080fd5b506100f5610464565b34801561012857600080fd5b50610131610469565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561017257600080fd5b506100f5600160a060020a036004351660243561049e565b34801561019657600080fd5b506100f5600160a060020a0360043516610570565b3480156101b757600080fd5b506100f56004356105f7565b3480156101cf57600080fd5b506100f561066f565b3480156101e457600080fd5b506101ed6106bd565b6040518082602080838360005b838110156102125781810151838201526020016101fa565b5050505090500191505060405180910390f35b34801561023157600080fd5b50610249600435600160a060020a03602435166106eb565b005b34801561025757600080fd5b506100f5600160a060020a03600435166107bd565b34801561027857600080fd5b50610284600435610844565b60408051600160a060020a039092168252519081900360200190f35b3480156102ac57600080fd5b506100f5600160a060020a0360043516610908565b3480156102cd57600080fd5b506100f561098f565b3480156102e257600080fd5b5061030260246004803582810192908201359181359182019101356109dd565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561034657818101518382015260200161032e565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561038557818101518382015260200161036d565b5050505090500194505050505060405180910390f35b604080517f0f09dbb26898a3af738d25c5fff308337ac8f2b0acbbaf209b373fb1389bcf2f602080830191909152606060020a600160a060020a038516028284015282516034818403018152605490920192839052815160009384938493909282918401908083835b602083106104235780518252601f199092019160209182019101610404565b51815160209384036101000a600019018019909216911617905260408051929094018290039091208652850195909552929092016000205495945050505050565b600181565b604080517f626c6f636b5265776172640000000000000000000000000000000000000000008152905190819003600b01902090565b604080517f24ae442c1f305c4f1294bf2dddd491a64250b2818b446706e9a74aeaaaf6f419602080830191909152606060020a600160a060020a0386160282840152605480830185905283518084039091018152607490920192839052815160009384938493909282918401908083835b6020831061052e5780518252601f19909201916020918201910161050f565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120865285019590955292909201600020549695505050505050565b604080517f0fd3be07b1332be84678873bf53feb10604cd09244fb4bb9154e03e00709b9e7602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b604080517f3840e646f7ce9b3210f5440e2dbd6b36451169bfdac65ef00a161729eded81bd60208083019190915281830184905282518083038401815260609092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b7f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2560009081526020527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c9515490565b6106c56112f3565b50604080516020810190915273bf3d6f830ce263cae987193982192cd990442b53815290565b60006106f633610baf565b151561070157600080fd5b82151561070d57600080fd5b600160a060020a038216151561072257600080fd5b61072b8261039b565b905080151561073d5761073d82610c1a565b610756610750828563ffffffff610d5716565b83610d6a565b6107786107728461076633610908565b9063ffffffff610d5716565b33610e34565b6040805184815290513391600160a060020a038516917f3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a9181900360200190a3505050565b604080517f12e71282a577e2b463da2c18bc96b6122db29bcef9065ed5a7f0f9316c11c08e602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b604080517fa47da669ec9f3749fbb12db00588b5fa6b5bbd24da81eb6cab44261334c21c1760208083019190915281830184905282518083038401815260609092019283905281516000936002938593909282918401908083835b602083106108be5780518252601f19909201916020918201910161089f565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912086528501959095529290920160002054600160a060020a031695945050505050565b604080517fa7f48dc57b1a051b1732e5ed136bbfd33bb5aa418e3e3498901320529e785461602080830191909152606060020a600160a060020a03851602828401528251603481840301815260549092019283905281516000938493849390928291840190808383602083106104235780518252601f199092019160209182019101610404565b7f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d4460009081526020527f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f5490565b60608060008180828080808033156109f457600080fd5b8c156109ff57600080fd5b8a15610a0a57600080fd5b610a1261098f565b975087604051908082528060200260200182016040528015610a3e578160200160208202803883390190505b50965087604051908082528060200260200182016040528015610a6b578160200160208202803883390190505b509550600094505b87851015610ae757610a8485610844565b9350610a8f8461039b565b9250610a9c600085610d6a565b838786815181101515610aab57fe5b600160a060020a0390921660209283029091019091015285518390879087908110610ad257fe5b60209081029091010152600190940193610a73565b600094505b87851015610b3757610b2c8686815181101515610b0557fe5b906020019060200201518887815181101515610b1d57fe5b90602001906020020151610ebb565b600190940193610aec565b600094505b6001851015610b9357610b4d6106bd565b8560018110610b5857fe5b60200201519150610b6882610908565b90506000811115610b8857610b7e600083610e34565b610b8881836111c4565b600190940193610b3c565b610b9b6112a4565b50949c939b50929950505050505050505050565b6000610bb96112f3565b6000610bc36106bd565b9150600090505b6001811015610c0e57818160018110610bdf57fe5b6020020151600160a060020a031684600160a060020a03161415610c065760019250610c13565b600101610bca565b600092505b5050919050565b6000610c2461098f565b604080517fa47da669ec9f3749fbb12db00588b5fa6b5bbd24da81eb6cab44261334c21c176020808301919091528183018490528251808303840181526060909201928390528151939450859360029360009392909182918401908083835b60208310610ca25780518252601f199092019160209182019101610c83565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912086528581019690965250929092016000908120805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03969096169590951790945550507f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d448252526001017f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f5550565b81810182811015610d6457fe5b92915050565b604080517f0f09dbb26898a3af738d25c5fff308337ac8f2b0acbbaf209b373fb1389bcf2f602080830191909152606060020a600160a060020a038516028284015282516034818403018152605490920192839052815185936000938493909282918401908083835b60208310610df25780518252601f199092019160209182019101610dd3565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120865285019590955292909201600020939093555050505050565b604080517fa7f48dc57b1a051b1732e5ed136bbfd33bb5aa418e3e3498901320529e785461602080830191909152606060020a600160a060020a0385160282840152825160348184030181526054909201928390528151859360009384939092829184019080838360208310610df25780518252601f199092019160209182019101610dd3565b604080517f24ae442c1f305c4f1294bf2dddd491a64250b2818b446706e9a74aeaaaf6f419602080830191909152606060020a600160a060020a038516028284015243605480840191909152835180840390910181526074909201928390528151600093918291908401908083835b60208310610f495780518252601f199092019160209182019101610f2a565b51815160209384036101000a60001901801990921691161790526040805192909401829003822060008181528083528590208a90557f0fd3be07b1332be84678873bf53feb10604cd09244fb4bb9154e03e00709b9e783830152600160a060020a038916606060020a02838601528451808403603401815260549093019485905282519097509195509293508392850191508083835b60208310610ffe5780518252601f199092019160209182019101610fdf565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600081815291829052929020549194506110469350909150859050610d57565b600082815260208181526040918290209290925580517f3840e646f7ce9b3210f5440e2dbd6b36451169bfdac65ef00a161729eded81bd818401524381830152815180820383018152606090910191829052805190928291908401908083835b602083106110c55780518252601f1990920191602091820191016110a6565b51815160209384036101000a600019018019909216911617905260408051929094018290039091206000818152918290529290205491945061110d9350909150859050610d57565b6000828152602081905260408120919091557f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2590527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c95154611174908463ffffffff610d5716565b7f076e79ca1c3a46f0c7d1e9e7f14bcb9716bfc49eed37baf510328301a7109c2560009081526020527fec9588bd3242595c0e33049f6384a658ee56f04f9b9e85c6cc9a045c4948c95155505050565b604080517f12e71282a577e2b463da2c18bc96b6122db29bcef9065ed5a7f0f9316c11c08e602080830191909152606060020a600160a060020a0385160282840152825160348184030181526054909201928390528151600093918291908401908083835b602083106112485780518252601f199092019160209182019101611229565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600081815291829052929020549194506112909350909150859050610d57565b600091825260208290526040909120555050565b7f0678259008a66390de8a5ac3f500d1dfb0d0f57018441e2cc69aaa0f52c97d44600090815260208190527f3f9dbe5402519a8ea505664ae3f65100b338acc0e57c0abec1fcff383511ac4f55565b60206040519081016040528060019060208202803883395091929150505600a165627a7a7230582053b0e89d867fc0c586739f4911c11be5aaee046320d1dff0da51c1b04404b4a00029",
+ "balance": "0x0"
+ },
+ "000000000000000000000000000000000000ffff": {
+ "code": "0x6080604052600436106100775763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633ad06d1681146100f657806354fd4d501461011c5780635c60da1b146101435780636fde820214610174578063a9c45fcb14610189578063f1739cae146101ad575b60006100816101ce565b9050600160a060020a03811615801561009f575061009d6101dd565b155b156100b7576100ac6101fe565b6100b46101ce565b90505b600160a060020a03811615156100cc57600080fd5b60405136600082376000803683855af43d82016040523d6000833e8080156100f2573d83f35b3d83fd5b34801561010257600080fd5b5061011a600435600160a060020a036024351661025f565b005b34801561012857600080fd5b50610131610289565b60408051918252519081900360200190f35b34801561014f57600080fd5b506101586101ce565b60408051600160a060020a039092168252519081900360200190f35b34801561018057600080fd5b5061015861028f565b61011a600480359060248035600160a060020a03169160443591820191013561029e565b3480156101b957600080fd5b5061011a600160a060020a0360043516610306565b600854600160a060020a031690565b60085474010000000000000000000000000000000000000000900460ff1690565b61021b73bf3d6f830ce263cae987193982192cd990442b5361038e565b610228600161fffe6103bd565b6008805474ff0000000000000000000000000000000000000000191674010000000000000000000000000000000000000000179055565b61026761028f565b600160a060020a0316331461027b57600080fd5b61028582826103bd565b5050565b60075490565b600654600160a060020a031690565b6102a661028f565b600160a060020a031633146102ba57600080fd5b6102c4848461025f565b30600160a060020a03163483836040518083838082843782019150509250505060006040518083038185875af192505050151561030057600080fd5b50505050565b61030e61028f565b600160a060020a0316331461032257600080fd5b600160a060020a038116151561033757600080fd5b7f5a3e66efaa1e445ebd894728a69d6959842ea1e97bd79b892797106e270efcd961036061028f565b60408051600160a060020a03928316815291841660208301528051918290030190a161038b8161038e565b50565b6006805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b600854600160a060020a03828116911614156103d857600080fd5b6103e181610462565b15156103ec57600080fd5b60075482116103fa57600080fd5b600782905560088054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff1990911681179091556040805184815290517f4289d6195cf3c2d2174adf98d0e19d4d2d08887995b99cb7b100e7ffe795820e9181900360200190a25050565b6000903b11905600a165627a7a72305820fe2b1a986fd29d79ccdcd85b780a42da827c47fdec4997a6e7f0d73b0daaafb00029",
+ "balance": "0x0"
+ }
+ },
+ "number": "0x0",
+ "gasUsed": "0x0",
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
+}
diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs
index 071f741a74..24b9edd909 100644
--- a/apps/indexer/config/config.exs
+++ b/apps/indexer/config/config.exs
@@ -38,6 +38,9 @@ config :indexer,
first_block: System.get_env("FIRST_BLOCK") || "0",
last_block: System.get_env("LAST_BLOCK") || ""
+config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor,
+ disabled?: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu"
+
# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true
config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true
diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs
new file mode 100644
index 0000000000..dc61acea33
--- /dev/null
+++ b/apps/indexer/config/dev/besu.exs
@@ -0,0 +1,30 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ else: EthereumJSONRPC.IPC,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs
new file mode 100644
index 0000000000..aad6529e5c
--- /dev/null
+++ b/apps/indexer/config/prod/besu.exs
@@ -0,0 +1,29 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/test/besu.exs b/apps/indexer/config/test/besu.exs
new file mode 100644
index 0000000000..ceaac3a9b4
--- /dev/null
+++ b/apps/indexer/config/test/besu.exs
@@ -0,0 +1,8 @@
+use Mix.Config
+
+config :indexer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex
index 1792b941ea..40ed68c6f6 100644
--- a/apps/indexer/lib/indexer/block/fetcher.ex
+++ b/apps/indexer/lib/indexer/block/fetcher.ex
@@ -434,7 +434,7 @@ defmodule Indexer.Block.Fetcher do
{:ok, beneficiary_address} = Chain.string_to_address_hash(beneficiary.address_hash)
"0x" <> minted_hex = beneficiary.reward
- {minted, _} = Integer.parse(minted_hex, 16)
+ {minted, _} = if minted_hex == "", do: {0, ""}, else: Integer.parse(minted_hex, 16)
if block_miner_payout_address && beneficiary_address.bytes == block_miner_payout_address.bytes do
gas_payment = gas_payment(beneficiary, transactions_by_block_number)
diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
index e9d914bd8c..4ed5a85888 100644
--- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
+++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
@@ -105,6 +105,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+ EthereumJSONRPC.Besu ->
+ EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+
_ ->
fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments)
end
diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
index e61d54d6f8..12acf46a09 100644
--- a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
+++ b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
@@ -440,8 +440,8 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
{:ok, responses}
end)
- bad_block_quantity = integer_to_quantity(bad_block_number)
- res_bad = eth_block_number_fake_response(bad_block_quantity)
+ good_block_quantity = integer_to_quantity(good_block_number)
+ res_good = eth_block_number_fake_response(good_block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
@@ -449,11 +449,11 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
- params: [bad_block_quantity, true]
+ params: [^good_block_quantity, true]
}
],
[] ->
- {:ok, [res_bad]}
+ {:ok, [res_good]}
end)
assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} =
diff --git a/docker/Makefile b/docker/Makefile
index e25b1f8e33..54803b94f0 100644
--- a/docker/Makefile
+++ b/docker/Makefile
@@ -220,6 +220,12 @@ endif
ifdef TXS_STATS_DAYS_TO_COMPILE_AT_INIT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_STATS_DAYS_TO_COMPILE_AT_INIT=$(TXS_STATS_DAYS_TO_COMPILE_AT_INIT)'
endif
+ifdef APPS_MENU
+ BLOCKSCOUT_CONTAINER_PARAMS += -e 'APPS_MENU=$(APPS_MENU)'
+endif
+ifdef EXTERNAL_APPS
+ BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXTERNAL_APPS=$(EXTERNAL_APPS)'
+endif
HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep ${DOCKER_IMAGE})
build:
diff --git a/mix.lock b/mix.lock
index a7df8d0f82..a9f52e64b3 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,5 +1,5 @@
%{
- "absinthe": {:hex, :absinthe, "1.5.1", "2f462f5849b2a4f72889d5a131ca6760b47ca8c5de2ba21c1dca3889634f2277", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b264eeb69605c6012f563e240edcca3d8abc5a725cab6f58ad82510a0283618b"},
+ "absinthe": {:hex, :absinthe, "1.5.2", "2f9449b0c135ea61c09c11968d3d4fe6abd5bed38cf9be1c6d6b7c5ec858cfa0", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669c84879629b7fffdc6cda9361ab9c81c9c7691e65418ba089b912a227963ac"},
"absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.0", "01c6a90af0ca12ee08d0fb93e23f9890d75bb6d3027f49ee4383bc03058ef5c3", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5.0", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "7ffbfe9fb82a14cafb78885cc2cef4f9d454bbbe2c95eec12b5463f5a20d1020"},
"absinthe_plug": {:hex, :absinthe_plug, "1.5.0", "018ef544cf577339018d1f482404b4bed762e1b530c78be9de4bbb88a6f3a805", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4c160f4ce9a1233a4219a42de946e4e05d0e8733537cd5d8d20e7d4ef8d4b7c7"},
"absinthe_relay": {:hex, :absinthe_relay, "1.5.0", "df4bc8891aa9d28dca31569d14c86c300a8c378a54fbc6e2ae9a45e5a3aa01fe", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "173cc44ccbf77c98cb4d2f05847b4c66f9ce2f436db16403c95e062b252b6081"},
@@ -23,7 +23,7 @@
"credo": {:hex, :credo, "1.1.2", "02b6422f3e659eb74b05aca3c20c1d8da0119a05ee82577a82e6c2938bf29f81", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd9322e9d391251ca3c4fac10ff0ce86ea4b99b3d9b34526ee2a7baa5928167d"},
"csv": {:hex, :csv, "2.3.1", "9ce11eff5a74a07baf3787b2b19dd798724d29a9c3a492a41df39f6af686da0e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "86626e1c89a4ad9a96d0d9c638f9e88c2346b89b4ba1611988594ebe72b5d5ee"},
"dataloader": {:hex, :dataloader, "1.0.7", "58351b335673cf40601429bfed6c11fece6ce7ad169b2ac0f0fe83e716587391", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "12bf66478e4a5085d09dc96932d058c206ee8c219cc7691d12a40dc35c8cefaa"},
- "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "bdf196feedfa6b83071e808b2b086fb113f8a1c4c7761f6eff6fe4b96aba0086"},
+ "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"decorator": {:hex, :decorator, "1.3.0", "6203dbd6e4e519a21a079e2a74e50fddaf03e80be22711b92eb4cd410173abea", [:mix], [], "hexpm", "1fde31b23897e529b44d72d798489e1c6d45936f106812a8df80180779afafb4"},
"deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"},
@@ -81,14 +81,14 @@
"optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
- "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
+ "phoenix": {:hex, :phoenix, "1.5.4", "0fca9ce7e960f9498d6315e41fcd0c80bfa6fbeb5fa3255b830c67fdfb7e703f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4e516d131fde87b568abd62e1b14aa07ba7d5edfd230bab4e25cc9dedbb39135"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
"phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.5", "d09aade160b584e3428e1e095645482396f17bddda4f566f1118f12d2598d11c", [:mix], [{:phoenix_html, "~> 2.10", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "acef2dbc638b5bcad92c11e41eb2b55d71f2596741a2f936717b8472196456ec"},
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
- "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.2.2", "7a09aa5d10e79b92d332a288f21cc49406b1b994cbda0fde76160e7f4cc890ea", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82364b29311dbad3753d588febd7e5ef05062cd6697d8c231e0e007adab3727"},
+ "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
@@ -108,7 +108,7 @@
"spandex_ecto": {:hex, :spandex_ecto, "0.4.0", "deaeaddc11a35f1c551206c53d09bb93a0da5808dbef751430e465c8c7de01d3", [:mix], [{:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "ef6037134548bba7a3b0efe39d8fc1a0ff95b43d6a046f193a552d311a637a97"},
"spandex_phoenix": {:hex, :spandex_phoenix, "0.3.2", "e81889d80852a895cf62ce2e25181b15766d21e8647962e0a4458414b935feb3", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "6e882bb64093889a73b7b77c426c1fd5651d072cd97e8f3e8d89d502d7b44b48"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
- "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
+ "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cf1345dfbce6acdfd4e23cbb36e96e53d1981bc89181cd0b936f4f398f4c0b78"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},