Gnosis safe proxy contract definition

pull/3420/head
Victor Baranov 4 years ago
parent de5e05c888
commit 50f8e9e506
  1. 1
      CHANGELOG.md
  2. 2
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  3. 6
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  4. 2
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  6. 10
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
  7. 106
      apps/explorer/lib/explorer/chain.ex

@ -1,6 +1,7 @@
## Current ## Current
### Features ### Features
- [#3420](https://github.com/poanetwork/blockscout/pull/3420) - Enable read/write proxy tabs for Gnosis safe proxy contract
- [#3411](https://github.com/poanetwork/blockscout/pull/3411) - Circles UBI theme - [#3411](https://github.com/poanetwork/blockscout/pull/3411) - Circles UBI theme
- [#3406](https://github.com/poanetwork/blockscout/pull/3406), [#3409](https://github.com/poanetwork/blockscout/pull/3409) - Adding mp4 files support for NFTs - [#3406](https://github.com/poanetwork/blockscout/pull/3406), [#3409](https://github.com/poanetwork/blockscout/pull/3409) - Adding mp4 files support for NFTs
- [#3398](https://github.com/poanetwork/blockscout/pull/3398) - Collect and display gas usage per day at the main page - [#3398](https://github.com/poanetwork/blockscout/pull/3398) - Collect and display gas usage per day at the main page

@ -89,7 +89,7 @@ defmodule BlockScoutWeb.SmartContractController do
with true <- ajax?(conn), with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
contract_type = if Chain.is_proxy_contract?(address.smart_contract.abi), do: :proxy, else: :regular contract_type = if Chain.proxy_contract?(address.smart_contract.abi), do: :proxy, else: :regular
outputs = outputs =
Reader.query_function( Reader.query_function(

@ -44,7 +44,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
&#8594; &#8594;
</div> </div>
<%= if queryable?(function["inputs"]) || writeable?(function) do %> <%= if queryable?(function["inputs"]) || writable?(function) do %>
<div style="width: 100%; overflow: hidden;"> <div style="width: 100%; overflow: hidden;">
<%= <%=
for status <- ["error", "warning", "success", "question"] do for status <- ["error", "warning", "success", "question"] do
@ -52,7 +52,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
end end
%> %>
<%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %>
<form class="form-inline" data-function-form data-action="<%= if writeable?(function), do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= @contract_abi %>" data-implementation-abi="<%= @implementation_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>"> <form class="form-inline" data-function-form data-action="<%= if writable?(function), do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, @address.hash) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= @contract_abi %>" data-implementation-abi="<%= @implementation_abi %>" data-chain-id="<%= Explorer.Chain.Cache.NetVersion.get_version() %>">
<input type="hidden" name="function_name" value='<%= function["name"] %>' /> <input type="hidden" name="function_name" value='<%= function["name"] %>' />
<input type="hidden" name="method_id" value='<%= function["method_id"] %>' /> <input type="hidden" name="method_id" value='<%= function["method_id"] %>' />
@ -70,7 +70,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
</div> </div>
<% end %> <% end %>
<input type="submit" value='<%= if writeable?(function), do: gettext("Write"), else: gettext("Query")%>' class="button btn-line button-xs py-0 mt-2 write-contract-btn" /> <input type="submit" value='<%= if writable?(function), do: gettext("Write"), else: gettext("Query")%>' class="button btn-line button-xs py-0 mt-2 write-contract-btn" />
</form> </form>
<%= if outputs?(function["outputs"]) do %> <%= if outputs?(function["outputs"]) do %>

@ -235,7 +235,7 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
Chain.is_proxy_contract?(address.smart_contract.abi) Chain.proxy_contract?(address.smart_contract.abi)
end end
def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false

@ -7,12 +7,12 @@ defmodule BlockScoutWeb.SmartContractView do
def queryable?(inputs) when is_nil(inputs), do: false def queryable?(inputs) when is_nil(inputs), do: false
def writeable?(function) when not is_nil(function), def writable?(function) when not is_nil(function),
do: do:
!constructor?(function) && !event?(function) && !constructor?(function) && !event?(function) &&
(payable?(function) || nonpayable?(function)) (payable?(function) || nonpayable?(function))
def writeable?(function) when is_nil(function), do: false def writable?(function) when is_nil(function), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)

@ -17,7 +17,7 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end end
end end
describe "writeable?" do describe "writable?" do
test "returns true when there is write function" do test "returns true when there is write function" do
function = %{ function = %{
"type" => "function", "type" => "function",
@ -29,7 +29,7 @@ defmodule BlockScoutWeb.SmartContractViewTest do
"constant" => false "constant" => false
} }
assert SmartContractView.writeable?(function) assert SmartContractView.writable?(function)
end end
test "returns false when it is not a write function" do test "returns false when it is not a write function" do
@ -43,19 +43,19 @@ defmodule BlockScoutWeb.SmartContractViewTest do
"constant" => true "constant" => true
} }
refute SmartContractView.writeable?(function) refute SmartContractView.writable?(function)
end end
test "returns false when there is no function" do test "returns false when there is no function" do
function = %{} function = %{}
refute SmartContractView.writeable?(function) refute SmartContractView.writable?(function)
end end
test "returns false when there function is nil" do test "returns false when there function is nil" do
function = nil function = nil
refute SmartContractView.writeable?(function) refute SmartContractView.writable?(function)
end end
end end

@ -5315,17 +5315,18 @@ defmodule Explorer.Chain do
[] []
end end
def is_proxy_contract?(abi) when not is_nil(abi) do def proxy_contract?(abi) when not is_nil(abi) do
implementation_method_abi = implementation_method_abi =
abi abi
|> Enum.find(fn method -> |> Enum.find(fn method ->
Map.get(method, "name") == "implementation" Map.get(method, "name") == "implementation" ||
master_copy_pattern?(method)
end) end)
if implementation_method_abi, do: true, else: false if implementation_method_abi, do: true, else: false
end end
def is_proxy_contract?(abi) when is_nil(abi) do def proxy_contract?(abi) when is_nil(abi) do
false false
end end
@ -5337,10 +5338,37 @@ defmodule Explorer.Chain do
Map.get(method, "name") == "implementation" Map.get(method, "name") == "implementation"
end) end)
implementation_method_abi_state_mutability = Map.get(implementation_method_abi, "stateMutability") implementation_method_abi_state_mutability =
implementation_method_abi && Map.get(implementation_method_abi, "stateMutability")
is_eip1967 = if implementation_method_abi_state_mutability == "nonpayable", do: true, else: false is_eip1967 = if implementation_method_abi_state_mutability == "nonpayable", do: true, else: false
if is_eip1967 do master_copy_method_abi =
abi
|> Enum.find(fn method ->
master_copy_pattern?(method)
end)
cond do
is_eip1967 ->
get_implementation_address_hash_eip_1967(proxy_address_hash)
implementation_method_abi ->
get_implementation_address_hash_basic(proxy_address_hash, abi)
master_copy_method_abi ->
get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash)
true ->
nil
end
end
def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
nil
end
defp get_implementation_address_hash_eip_1967(proxy_address_hash) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
# https://eips.ethereum.org/EIPS/eip-1967 # https://eips.ethereum.org/EIPS/eip-1967
@ -5354,12 +5382,10 @@ defmodule Explorer.Chain do
json_rpc_named_arguments json_rpc_named_arguments
) )
if String.length(implementation_address) > 42 do abi_decode_address_output(implementation_address)
"0x" <> String.slice(implementation_address, -40, 40)
else
implementation_address
end end
else
defp get_implementation_address_hash_basic(proxy_address_hash, abi) do
# 5c60da1b = keccak256(implementation()) # 5c60da1b = keccak256(implementation())
implementation_address = implementation_address =
case Reader.query_contract(proxy_address_hash, abi, %{ case Reader.query_contract(proxy_address_hash, abi, %{
@ -5369,20 +5395,62 @@ defmodule Explorer.Chain do
_ -> nil _ -> nil
end end
if implementation_address do address_to_hex(implementation_address)
if String.starts_with?(implementation_address, "0x") do
implementation_address
else
"0x" <> Base.encode16(implementation_address, case: :lower)
end end
else
nil defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
master_copy_storage_pointer = "0x0"
{:ok, implementation_address} =
Contract.eth_get_storage_at_request(
proxy_address_hash,
master_copy_storage_pointer,
nil,
json_rpc_named_arguments
)
abi_decode_address_output(implementation_address)
end
defp master_copy_pattern?(method) do
Map.get(method, "type") == "constructor" &&
method
|> Enum.find(fn item ->
case item do
{"inputs", inputs} ->
master_copy_input?(inputs)
_ ->
false
end end
end)
end end
defp master_copy_input?(inputs) do
inputs
|> Enum.find(fn input ->
Map.get(input, "name") == "_masterCopy"
end)
end end
def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do defp abi_decode_address_output(address) do
nil if String.length(address) > 42 do
"0x" <> String.slice(address, -40, 40)
else
address
end
end
defp address_to_hex(address) do
if address do
if String.starts_with?(address, "0x") do
address
else
"0x" <> Base.encode16(address, case: :lower)
end
end
end end
def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do

Loading…
Cancel
Save