Merge pull request #3153 from poanetwork/vb-decoding-using-implementation-abi

Proxy contracts: methods decoding using implementation ABI
pull/3157/head
Victor Baranov 5 years ago committed by GitHub
commit 003389ec8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 51
      apps/explorer/lib/explorer/chain.ex
  3. 9
      apps/explorer/lib/explorer/chain/log.ex
  4. 19
      apps/explorer/lib/explorer/chain/transaction.ex
  5. 172
      apps/explorer/test/explorer/chain_test.exs

@ -1,6 +1,8 @@
## Current ## Current
### Features ### Features
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: logs decoding using implementation ABI
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: methods decoding using implementation ABI
- [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint. - [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint.
### Fixes ### Fixes

@ -74,6 +74,7 @@ defmodule Explorer.Chain do
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter} alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.Reader
alias Dataloader.Ecto, as: DataloaderEcto alias Dataloader.Ecto, as: DataloaderEcto
@ -4337,6 +4338,56 @@ defmodule Explorer.Chain do
end end
end end
def combine_proxy_implementation_abi(address_hash, abi) when not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
implementation_abi =
if implementation_method_abi do
implementation_address =
case Reader.query_contract(address_hash, abi, %{
"implementation" => []
}) do
%{"implementation" => {:ok, [result]}} -> result
_ -> nil
end
if implementation_address do
implementation_address_hash_string = "0x" <> Base.encode16(implementation_address, case: :lower)
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
else
[]
end
else
[]
end
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
[]
end
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} = {:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do if Map.has_key?(first_trace, :to_address_hash) do

@ -6,8 +6,8 @@ defmodule Explorer.Chain.Log do
require Logger require Logger
alias ABI.{Event, FunctionSelector} alias ABI.{Event, FunctionSelector}
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction} alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Repo
@required_attrs ~w(address_hash data block_hash index transaction_hash)a @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 @optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
@ -121,8 +121,11 @@ defmodule Explorer.Chain.Log do
""" """
def decode(_log, %Transaction{to_address: nil}), do: {:error, :no_to_address} 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 def decode(log, transaction = %Transaction{to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}}})
with {:ok, selector, mapping} <- find_and_decode(abi, log, transaction), when not is_nil(abi) do
full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
identifier <- Base.encode16(selector.method_id, case: :lower), identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping), text <- function_call(selector.function, mapping),
do: {:ok, identifier, text, mapping} do: {:ok, identifier, text, mapping}

@ -11,6 +11,8 @@ defmodule Explorer.Chain.Transaction do
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Block, Block,
@ -26,7 +28,6 @@ defmodule Explorer.Chain.Transaction do
} }
alias Explorer.Chain.Transaction.{Fork, Status} 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 @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 error gas_used index created_contract_code_indexed_at status
@ -423,7 +424,7 @@ defmodule Explorer.Chain.Transaction do
candidates_query candidates_query
|> Repo.all() |> Repo.all()
|> Enum.flat_map(fn candidate -> |> 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] {:ok, _, _, _} = decoded -> [decoded]
_ -> [] _ -> []
end end
@ -436,12 +437,18 @@ defmodule Explorer.Chain.Transaction do
{:error, :contract_not_verified, []} {:error, :contract_not_verified, []}
end end
def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do def decoded_input_data(%__MODULE__{
do_decoded_input_data(data, abi, hash) 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 end
defp do_decoded_input_data(data, abi, hash) do defp do_decoded_input_data(data, abi, address_hash, hash) do
with {:ok, {selector, values}} <- find_and_decode(abi, data, hash), 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), {:ok, mapping} <- selector_mapping(selector, values, hash),
identifier <- Base.encode16(selector.method_id, case: :lower), identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping), text <- function_call(selector.function, mapping),

@ -5193,4 +5193,176 @@ defmodule Explorer.ChainTest do
assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type" assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
end end
end end
describe "combine_proxy_implementation_abi/2" 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 "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 "returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "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 "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
end
end end

Loading…
Cancel
Save