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. 142
      apps/explorer/lib/explorer/chain.ex

@ -1,6 +1,7 @@
## Current
### 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
- [#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

@ -89,7 +89,7 @@ defmodule BlockScoutWeb.SmartContractController do
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
{: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 =
Reader.query_function(

@ -44,7 +44,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
&#8594;
</div>
<%= if queryable?(function["inputs"]) || writeable?(function) do %>
<%= if queryable?(function["inputs"]) || writable?(function) do %>
<div style="width: 100%; overflow: hidden;">
<%=
for status <- ["error", "warning", "success", "question"] do
@ -52,7 +52,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
end
%>
<%= 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="method_id" value='<%= function["method_id"] %>' />
@ -70,7 +70,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
</div>
<% 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>
<%= 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_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
Chain.is_proxy_contract?(address.smart_contract.abi)
Chain.proxy_contract?(address.smart_contract.abi)
end
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 writeable?(function) when not is_nil(function),
def writable?(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 writable?(function) when is_nil(function), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)

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

@ -5315,17 +5315,18 @@ defmodule Explorer.Chain do
[]
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 =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
Map.get(method, "name") == "implementation" ||
master_copy_pattern?(method)
end)
if implementation_method_abi, do: true, else: false
end
def is_proxy_contract?(abi) when is_nil(abi) do
def proxy_contract?(abi) when is_nil(abi) do
false
end
@ -5337,47 +5338,29 @@ defmodule Explorer.Chain do
Map.get(method, "name") == "implementation"
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
if is_eip1967 do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
master_copy_method_abi =
abi
|> Enum.find(fn method ->
master_copy_pattern?(method)
end)
# https://eips.ethereum.org/EIPS/eip-1967
eip_1967_implementation_storage_pointer = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
cond do
is_eip1967 ->
get_implementation_address_hash_eip_1967(proxy_address_hash)
{:ok, implementation_address} =
Contract.eth_get_storage_at_request(
proxy_address_hash,
eip_1967_implementation_storage_pointer,
nil,
json_rpc_named_arguments
)
implementation_method_abi ->
get_implementation_address_hash_basic(proxy_address_hash, abi)
if String.length(implementation_address) > 42 do
"0x" <> String.slice(implementation_address, -40, 40)
else
implementation_address
end
else
# 5c60da1b = keccak256(implementation())
implementation_address =
case Reader.query_contract(proxy_address_hash, abi, %{
"5c60da1b" => []
}) do
%{"5c60da1b" => {:ok, [result]}} -> result
_ -> nil
end
master_copy_method_abi ->
get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash)
if implementation_address do
if String.starts_with?(implementation_address, "0x") do
implementation_address
else
"0x" <> Base.encode16(implementation_address, case: :lower)
end
else
true ->
nil
end
end
end
@ -5385,6 +5368,91 @@ defmodule Explorer.Chain 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)
# 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
)
abi_decode_address_output(implementation_address)
end
defp get_implementation_address_hash_basic(proxy_address_hash, abi) do
# 5c60da1b = keccak256(implementation())
implementation_address =
case Reader.query_contract(proxy_address_hash, abi, %{
"5c60da1b" => []
}) do
%{"5c60da1b" => {:ok, [result]}} -> result
_ -> nil
end
address_to_hex(implementation_address)
end
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
defp master_copy_input?(inputs) do
inputs
|> Enum.find(fn input ->
Map.get(input, "name") == "_masterCopy"
end)
end
defp abi_decode_address_output(address) do
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
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} ->

Loading…
Cancel
Save