Skip to content

Commit

Permalink
Fix contract constructor require msg appearance in constructor argume…
Browse files Browse the repository at this point in the history
…nts encoded view
  • Loading branch information
vbaranov committed Jan 25, 2020
1 parent 677a0fb commit 87092a4
Show file tree
Hide file tree
Showing 4 changed files with 662 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
24 changes: 18 additions & 6 deletions apps/explorer/lib/explorer/smart_contract/verifier.ex
Expand Up @@ -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)

Expand All @@ -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}}
Expand All @@ -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 ->
Expand Down
Expand Up @@ -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 =
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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 find_constructor_arguments(address_hash, abi) do
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, contract_source_code) do
creation_code =
address_hash
|> Chain.contract_creation_input_data()
Expand All @@ -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

0 comments on commit 87092a4

Please sign in to comment.