Fix contract constructor require msg appearance in constructor arguments encoded view

pull/2969/head
Victor Baranov 5 years ago
parent 677a0fba8a
commit 87092a4fa2
  1. 1
      CHANGELOG.md
  2. 24
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  3. 99
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  4. 559
      apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs

@ -6,6 +6,7 @@
- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action explicitly
### Fixes
- [#2969](https://github.com/poanetwork/blockscout/pull/2969) - Fix contract constructor require msg appearance in constructor arguments encoded view
- [#2964](https://github.com/poanetwork/blockscout/pull/2964) - Fix bug in skipping of constructor arguments in contract verification
- [#2961](https://github.com/poanetwork/blockscout/pull/2961) - Add a guard that addresses is enum in `values` function in `read contract` page
- [#2960](https://github.com/poanetwork/blockscout/pull/2960) - Add BLOCKSCOUT_HOST to docker setup

@ -54,18 +54,25 @@ defmodule Explorer.SmartContract.Verifier do
external_libs: external_libraries
)
compare_bytecodes(solc_output, address_hash, constructor_arguments, autodetect_contructor_arguments)
compare_bytecodes(
solc_output,
address_hash,
constructor_arguments,
autodetect_contructor_arguments,
contract_source_code
)
end
defp compare_bytecodes({:error, :name}, _, _, _), do: {:error, :name}
defp compare_bytecodes({:error, _}, _, _, _), do: {:error, :compilation}
defp compare_bytecodes({:error, :name}, _, _, _, _), do: {:error, :name}
defp compare_bytecodes({:error, _}, _, _, _, _), do: {:error, :compilation}
# credo:disable-for-next-line /Complexity/
defp compare_bytecodes(
{:ok, %{"abi" => abi, "bytecode" => bytecode}},
address_hash,
arguments_data,
autodetect_contructor_arguments
autodetect_contructor_arguments,
contract_source_code
) do
generated_bytecode = extract_bytecode(bytecode)
@ -82,7 +89,7 @@ defmodule Explorer.SmartContract.Verifier do
{:error, :generated_bytecode}
has_constructor_with_params?(abi) && autodetect_contructor_arguments ->
result = ConstructorArguments.find_constructor_arguments(address_hash, abi)
result = ConstructorArguments.find_constructor_arguments(address_hash, abi, contract_source_code)
if result do
{:ok, %{abi: abi, contructor_arguments: result}}
@ -94,7 +101,12 @@ defmodule Explorer.SmartContract.Verifier do
{:error, :constructor_arguments}
has_constructor_with_params?(abi) &&
!ConstructorArguments.verify(address_hash, blockchain_bytecode_without_whisper, arguments_data) ->
!ConstructorArguments.verify(
address_hash,
blockchain_bytecode_without_whisper,
arguments_data,
contract_source_code
) ->
{:error, :constructor_arguments}
true ->

@ -5,7 +5,7 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain
def verify(address_hash, contract_code, arguments_data) do
def verify(address_hash, contract_code, arguments_data, contract_source_code) do
arguments_data = arguments_data |> String.trim_trailing() |> String.trim_leading("0x")
creation_code =
@ -18,7 +18,7 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
if verify_older_version(creation_code, contract_code, check_func) do
true
else
extract_constructor_arguments(creation_code, check_func)
extract_constructor_arguments(creation_code, check_func, contract_source_code)
end
end
@ -31,22 +31,22 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
|> check_func.()
end
defp extract_constructor_arguments(code, check_func) do
defp extract_constructor_arguments(code, check_func, contract_source_code) do
case code do
# Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html
"a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func)
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code)
# Solidity >= 0.5.10 https://solidity.readthedocs.io/en/v0.5.10/metadata.html
"a265627a7a72305820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func)
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code)
# Solidity >= 0.5.11 https://github.com/ethereum/solidity/blob/develop/Changelog.md#0511-2019-08-12
# Metadata: Update the swarm hash to the current specification, changes bzzr0 to bzzr1 and urls to use bzz-raw://
"a265627a7a72315820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func)
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code)
# 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
@ -60,27 +60,47 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
# Fixing PR has been created https://github.com/ethereum/solidity/pull/8174
"a264697066735822" <>
<<_::binary-size(68)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0033" <> constructor_arguments ->
extract_constructor_arguments_check_func(constructor_arguments, check_func)
extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code)
<<>> ->
check_func.("")
<<_::binary-size(2)>> <> rest ->
extract_constructor_arguments(rest, check_func)
extract_constructor_arguments(rest, check_func, contract_source_code)
end
end
defp extract_constructor_arguments_check_func(constructor_arguments, check_func) do
defp extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code) do
constructor_arguments =
remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments)
check_func_result = check_func.(constructor_arguments)
if check_func_result do
check_func_result
else
extract_constructor_arguments(constructor_arguments, check_func)
extract_constructor_arguments(constructor_arguments, check_func, contract_source_code)
end
end
def remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments) do
msgs_list =
contract_source_code
|> extract_require_messages_from_constructor()
|> Enum.reverse()
Enum.reduce(msgs_list, constructor_arguments, fn msg, pure_constructor_arguments ->
case String.split(pure_constructor_arguments, msg, parts: 2) do
[_, constructor_arguments_part] ->
constructor_arguments_part
[_] ->
pure_constructor_arguments
end
end)
end
def find_constructor_arguments(address_hash, abi) do
def find_constructor_arguments(address_hash, abi, contract_source_code) do
creation_code =
address_hash
|> Chain.contract_creation_input_data()
@ -103,6 +123,61 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
end
end
extract_constructor_arguments(creation_code, check_func)
extract_constructor_arguments(creation_code, check_func, contract_source_code)
end
def extract_require_messages_from_constructor(contract_source_code) do
constructor = find_constructor_content(contract_source_code)
require_contents = find_require_in_constructor(constructor)
messages_list =
Enum.reduce(require_contents, [], fn require_content, msgs ->
msg = get_require_message_hex(require_content)
if msg, do: [msg | msgs]
end)
if messages_list, do: messages_list, else: []
end
def find_constructor_content(contract_source_code) do
case String.split(contract_source_code, "constructor", parts: 2) do
[_, right_from_contstructor] ->
[_, right_from_contstructor_inside] = String.split(right_from_contstructor, "{", parts: 2)
[constructor, _] = String.split(right_from_contstructor_inside, "}", parts: 2)
constructor
[_] ->
nil
end
end
def find_require_in_constructor(constructor) do
if constructor do
[_ | requires] = String.split(constructor, "require")
Enum.reduce(requires, [], fn right_from_require, requires_list ->
[_ | [right_from_require_inside]] = String.split(right_from_require, "(", parts: 2)
[require_content | _] = String.split(right_from_require_inside, ");", parts: 2)
[require_content | requires_list]
end)
else
[]
end
end
def get_require_message_hex(require_content) do
case String.split(require_content, ",", parts: 2) do
[_ | [msg]] ->
msg
|> String.trim()
|> String.trim_leading("\"")
|> String.trim_trailing("\"")
|> String.trim_leading("'")
|> String.trim_trailing("'")
|> Base.encode16(case: :lower)
[_] ->
nil
end
end
end

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save