diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2a8dcb2f..4aa12cfdef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code - [#1896](https://github.com/poanetwork/blockscout/pull/1896) - re-query tokens in top nav automplete - [#1881](https://github.com/poanetwork/blockscout/pull/1881) - fix: store solc versions locally for performance +- [#1875](https://github.com/poanetwork/blockscout/pull/1875) - fix: resolve false positive constructor arguments - [#1904](https://github.com/poanetwork/blockscout/pull/1904) - fix `BLOCK_COUNT_CACHE_TTL` env var type - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index 72c5bae186..656f1b9734 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -5,6 +5,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do alias Explorer.SmartContract.SolcDownloader + require Logger + @new_contract_name "New.sol" @allowed_evm_versions ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg"] @@ -63,6 +65,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do } } """ + @spec run(Keyword.t()) :: {:ok, map} | {:error, :compilation | :name} def run(params) do name = Keyword.fetch!(params, :name) compiler_version = Keyword.fetch!(params, :compiler_version) @@ -100,7 +103,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do ] ) - with {:ok, contracts} <- Jason.decode(response), + with {:ok, decoded} <- Jason.decode(response), + {:ok, contracts} <- get_contracts(decoded), %{"abi" => abi, "evm" => %{"deployedBytecode" => %{"object" => bytecode}}} <- get_contract_info(contracts, name) do {:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}} @@ -108,9 +112,16 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do {:error, %Jason.DecodeError{}} -> {:error, :compilation} + {:error, reason} when reason in [:name, :compilation] -> + {:error, reason} + error -> - parse_error(error) + error = parse_error(error) + Logger.warn(["There was an error compiling a provided contract: ", inspect(error)]) + {:error, :compilation} end + else + {:error, :compilation} end end @@ -133,10 +144,15 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do end end - def parse_error(%{"error" => error}), do: {:error, [error]} - def parse_error(%{"errors" => errors}), do: {:error, errors} + def parse_error({:error, %{"error" => error}}), do: {:error, [error]} + def parse_error({:error, %{"errors" => errors}}), do: {:error, errors} def parse_error({:error, _} = error), do: error + # Older solc-bin versions don't use filename as contract key + defp get_contracts(%{"contracts" => %{"New.sol" => contracts}}), do: {:ok, contracts} + defp get_contracts(%{"contracts" => %{"" => contracts}}), do: {:ok, contracts} + defp get_contracts(response), do: {:error, response} + defp optimize_value(false), do: "0" defp optimize_value("false"), do: "0" diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index 4b521fba35..e72324f154 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -86,17 +86,32 @@ defmodule Explorer.SmartContract.Verifier do In order to discover the bytecode we need to remove the `swarm source` from the hash. - `64` characters to the left of `0029` are the `swarm source`. The rest on - the left is the `bytecode` to be validated. + For more information on the swarm hash, check out: + https://solidity.readthedocs.io/en/v0.5.3/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode """ + def extract_bytecode("0x" <> code) do + "0x" <> extract_bytecode(code) + end + def extract_bytecode(code) do - {bytecode, _swarm_source} = - code - |> String.split("0029") - |> List.first() - |> String.split_at(-64) + do_extract_bytecode([], String.downcase(code)) + end - bytecode + defp do_extract_bytecode(extracted, remaining) do + case remaining do + <<>> -> + extracted + |> Enum.reverse() + |> :binary.list_to_bin() + + "a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> _constructor_arguments -> + extracted + |> Enum.reverse() + |> :binary.list_to_bin() + + <> <> rest -> + do_extract_bytecode([next | extracted], rest) + end end def next_evm_version(current_evm_version) do diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex index b60bd01927..aa32d85bb6 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -7,20 +7,21 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do def verify(address_hash, arguments_data) do arguments_data = String.replace(arguments_data, "0x", "") - creation_input_data = Chain.contract_creation_input_data(address_hash) - data_with_swarm = - creation_input_data - |> String.split("0029") - |> List.first() - |> Kernel.<>("0029") + address_hash + |> Chain.contract_creation_input_data() + |> String.replace("0x", "") + |> extract_constructor_arguments() + |> Kernel.==(arguments_data) + end + + defp extract_constructor_arguments(<<>>), do: "" - expected_arguments_data = - creation_input_data - |> String.split(data_with_swarm) - |> List.last() - |> String.replace("0x", "") + defp extract_constructor_arguments("a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments) do + constructor_arguments + end - expected_arguments_data == arguments_data + defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do + extract_constructor_arguments(rest) end end diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js index 5aaf3f54d7..eea727802e 100755 --- a/apps/explorer/priv/compile_solc.js +++ b/apps/explorer/priv/compile_solc.js @@ -39,6 +39,4 @@ const input = { const output = JSON.parse(solc.compile(JSON.stringify(input))) -/** Older solc-bin versions don't use filename as contract key */ -const response = output.contracts[newContractName] || output.contracts[''] -console.log(JSON.stringify(response)); +console.log(JSON.stringify(output)); diff --git a/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs b/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs index 9fe219719d..a5e019c86f 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs +++ b/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs @@ -7,17 +7,19 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArgumentsTest do alias Explorer.SmartContract.Verifier.ConstructorArguments test "veriies constructor constructor arguments with whisper data" do - constructor_arguments = "0x0405" + constructor_arguments = Base.encode16(:crypto.strong_rand_bytes(64), case: :lower) address = insert(:address) - input = %Data{ - bytes: - <<1, 2, 3, 93, 148, 60, 87, 91, 232, 162, 174, 226, 187, 119, 55, 167, 101, 253, 210, 198, 228, 155, 116, 205, - 44, 146, 171, 15, 168, 228, 40, 45, 26, 117, 174, 0, 41, 4, 5>> + input = + "a165627a7a72305820" <> + Base.encode16(:crypto.strong_rand_bytes(32), case: :lower) <> "0029" <> constructor_arguments + + input_data = %Data{ + bytes: Base.decode16!(input, case: :lower) } :transaction - |> insert(created_contract_address_hash: address.hash, input: input) + |> insert(created_contract_address_hash: address.hash, input: input_data) |> with_block() assert ConstructorArguments.verify(address.hash, constructor_arguments) diff --git a/apps/explorer/test/explorer/smart_contract/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/verifier_test.exs index 1e8301a90c..ef635c2573 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/verifier_test.exs @@ -156,7 +156,7 @@ defmodule Explorer.SmartContract.VerifierTest do swarm_source = "3c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b" bytecode = - "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820" + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600" assert bytecode == Verifier.extract_bytecode(code) assert bytecode != code @@ -164,5 +164,21 @@ defmodule Explorer.SmartContract.VerifierTest do assert String.contains?(bytecode, "0029") == false assert String.contains?(bytecode, swarm_source) == false end + + test "extracts everything to the left of the swarm hash" do + code = + "0x608060405234801561001057600080fd5b5060df80610010029f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a723058203c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b0029" + + swarm_source = "3c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b" + + bytecode = + "0x608060405234801561001057600080fd5b5060df80610010029f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600" + + assert bytecode == Verifier.extract_bytecode(code) + assert bytecode != code + assert String.contains?(code, bytecode) == true + assert String.contains?(bytecode, "0029") == true + assert String.contains?(bytecode, swarm_source) == false + end end end