Merge branch 'staking-app-changes' of https://github.com/poanetwork/blockscout into staking-app-changes

pull/1916/head
saneery 6 years ago
commit 131a365c86
  1. 1
      CHANGELOG.md
  2. 24
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  3. 31
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  4. 25
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  5. 4
      apps/explorer/priv/compile_solc.js
  6. 14
      apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs
  7. 18
      apps/explorer/test/explorer/smart_contract/verifier_test.exs

@ -25,6 +25,7 @@
- [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code - [#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 - [#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 - [#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 - [#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 - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments

@ -5,6 +5,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
alias Explorer.SmartContract.SolcDownloader alias Explorer.SmartContract.SolcDownloader
require Logger
@new_contract_name "New.sol" @new_contract_name "New.sol"
@allowed_evm_versions ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg"] @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 def run(params) do
name = Keyword.fetch!(params, :name) name = Keyword.fetch!(params, :name)
compiler_version = Keyword.fetch!(params, :compiler_version) 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}}} <- %{"abi" => abi, "evm" => %{"deployedBytecode" => %{"object" => bytecode}}} <-
get_contract_info(contracts, name) do get_contract_info(contracts, name) do
{:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}} {:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}}
@ -108,9 +112,16 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
{:error, %Jason.DecodeError{}} -> {:error, %Jason.DecodeError{}} ->
{:error, :compilation} {:error, :compilation}
{:error, reason} when reason in [:name, :compilation] ->
{:error, reason}
error -> error ->
parse_error(error) error = parse_error(error)
Logger.warn(["There was an error compiling a provided contract: ", inspect(error)])
{:error, :compilation}
end end
else
{:error, :compilation}
end end
end end
@ -133,10 +144,15 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
end end
end end
def parse_error(%{"error" => error}), do: {:error, [error]} def parse_error({:error, %{"error" => error}}), do: {:error, [error]}
def parse_error(%{"errors" => errors}), do: {:error, errors} def parse_error({:error, %{"errors" => errors}}), do: {:error, errors}
def parse_error({:error, _} = error), do: error 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"
defp optimize_value("false"), do: "0" defp optimize_value("false"), do: "0"

@ -86,17 +86,32 @@ defmodule Explorer.SmartContract.Verifier do
In order to discover the bytecode we need to remove the `swarm source` from In order to discover the bytecode we need to remove the `swarm source` from
the hash. the hash.
`64` characters to the left of `0029` are the `swarm source`. The rest on For more information on the swarm hash, check out:
the left is the `bytecode` to be validated. 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 def extract_bytecode(code) do
{bytecode, _swarm_source} = do_extract_bytecode([], String.downcase(code))
code end
|> String.split("0029")
|> List.first()
|> String.split_at(-64)
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()
<<next::binary-size(2)>> <> rest ->
do_extract_bytecode([next | extracted], rest)
end
end end
def next_evm_version(current_evm_version) do def next_evm_version(current_evm_version) do

@ -7,20 +7,21 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
def verify(address_hash, arguments_data) do def verify(address_hash, arguments_data) do
arguments_data = String.replace(arguments_data, "0x", "") arguments_data = String.replace(arguments_data, "0x", "")
creation_input_data = Chain.contract_creation_input_data(address_hash)
data_with_swarm = address_hash
creation_input_data |> Chain.contract_creation_input_data()
|> String.split("0029") |> String.replace("0x", "")
|> List.first() |> extract_constructor_arguments()
|> Kernel.<>("0029") |> Kernel.==(arguments_data)
end
defp extract_constructor_arguments(<<>>), do: ""
expected_arguments_data = defp extract_constructor_arguments("a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments) do
creation_input_data constructor_arguments
|> String.split(data_with_swarm) end
|> List.last()
|> String.replace("0x", "")
expected_arguments_data == arguments_data defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do
extract_constructor_arguments(rest)
end end
end end

@ -39,6 +39,4 @@ const input = {
const output = JSON.parse(solc.compile(JSON.stringify(input))) const output = JSON.parse(solc.compile(JSON.stringify(input)))
/** Older solc-bin versions don't use filename as contract key */ console.log(JSON.stringify(output));
const response = output.contracts[newContractName] || output.contracts['']
console.log(JSON.stringify(response));

@ -7,17 +7,19 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArgumentsTest do
alias Explorer.SmartContract.Verifier.ConstructorArguments alias Explorer.SmartContract.Verifier.ConstructorArguments
test "veriies constructor constructor arguments with whisper data" do 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) address = insert(:address)
input = %Data{ input =
bytes: "a165627a7a72305820" <>
<<1, 2, 3, 93, 148, 60, 87, 91, 232, 162, 174, 226, 187, 119, 55, 167, 101, 253, 210, 198, 228, 155, 116, 205, Base.encode16(:crypto.strong_rand_bytes(32), case: :lower) <> "0029" <> constructor_arguments
44, 146, 171, 15, 168, 228, 40, 45, 26, 117, 174, 0, 41, 4, 5>>
input_data = %Data{
bytes: Base.decode16!(input, case: :lower)
} }
:transaction :transaction
|> insert(created_contract_address_hash: address.hash, input: input) |> insert(created_contract_address_hash: address.hash, input: input_data)
|> with_block() |> with_block()
assert ConstructorArguments.verify(address.hash, constructor_arguments) assert ConstructorArguments.verify(address.hash, constructor_arguments)

@ -156,7 +156,7 @@ defmodule Explorer.SmartContract.VerifierTest do
swarm_source = "3c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b" swarm_source = "3c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b"
bytecode = bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820" "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600"
assert bytecode == Verifier.extract_bytecode(code) assert bytecode == Verifier.extract_bytecode(code)
assert bytecode != code assert bytecode != code
@ -164,5 +164,21 @@ defmodule Explorer.SmartContract.VerifierTest do
assert String.contains?(bytecode, "0029") == false assert String.contains?(bytecode, "0029") == false
assert String.contains?(bytecode, swarm_source) == false assert String.contains?(bytecode, swarm_source) == false
end 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
end end

Loading…
Cancel
Save