diff --git a/CHANGELOG.md b/CHANGELOG.md index ed4b8db4d5..21b902a6a1 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 +- [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments ### Chore diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index 0fc9f6b21b..4b521fba35 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -74,7 +74,7 @@ defmodule Explorer.SmartContract.Verifier do generated_bytecode != blockchain_bytecode_without_whisper -> {:error, :generated_bytecode} - !ConstructorArguments.verify(address_hash, arguments_data) -> + has_constructor_with_params?(abi) && !ConstructorArguments.verify(address_hash, arguments_data) -> {:error, :constructor_arguments} true -> @@ -111,4 +111,8 @@ defmodule Explorer.SmartContract.Verifier do prev_version end end + + defp has_constructor_with_params?(abi) do + Enum.any?(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) + end end diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs index 79bbb62f70..57f15577ac 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs @@ -173,6 +173,101 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do assert {:error, :compilation} = response end + + test "returns constructor in abi" do + code = """ + pragma solidity ^0.4.22; + + contract OwnedToken { + // TokenCreator is a contract type that is defined below. + // It is fine to reference it as long as it is not used + // to create a new contract. + TokenCreator creator; + address owner; + bytes32 name; + + // This is the constructor which registers the + // creator and the assigned name. + constructor(bytes32 _name) public { + // State variables are accessed via their name + // and not via e.g. this.owner. This also applies + // to functions and especially in the constructors, + // you can only call them like that ("internally"), + // because the contract itself does not exist yet. + owner = msg.sender; + // We do an explicit type conversion from `address` + // to `TokenCreator` and assume that the type of + // the calling contract is TokenCreator, there is + // no real way to check that. + creator = TokenCreator(msg.sender); + name = _name; + } + + function changeName(bytes32 newName) public { + // Only the creator can alter the name -- + // the comparison is possible since contracts + // are implicitly convertible to addresses. + if (msg.sender == address(creator)) + name = newName; + } + + function transfer(address newOwner) public { + // Only the current owner can transfer the token. + if (msg.sender != owner) return; + // We also want to ask the creator if the transfer + // is fine. Note that this calls a function of the + // contract defined below. If the call fails (e.g. + // due to out-of-gas), the execution here stops + // immediately. + if (creator.isTokenTransferOK(owner, newOwner)) + owner = newOwner; + } + } + + contract TokenCreator { + function createToken(bytes32 name) + public + returns (OwnedToken tokenAddress) + { + // Create a new Token contract and return its address. + // From the JavaScript side, the return type is simply + // `address`, as this is the closest type available in + // the ABI. + return new OwnedToken(name); + } + + function changeName(OwnedToken tokenAddress, bytes32 name) public { + // Again, the external type of `tokenAddress` is + // simply `address`. + tokenAddress.changeName(name); + } + + function isTokenTransferOK(address currentOwner, address newOwner) + public + view + returns (bool ok) + { + // Check some arbitrary condition. + address tokenAddress = msg.sender; + return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); + } + } + """ + + name = "OwnedToken" + compiler_version = "v0.4.22+commit.4cb486ee" + + {:ok, %{"abi" => abi}} = + CodeCompiler.run( + name: name, + compiler_version: compiler_version, + code: code, + evm_version: "byzantium", + optimize: true + ) + + assert Enum.any?(abi, fn el -> el["type"] == "constructor" end) + end end describe "get_contract_info/1" do diff --git a/apps/explorer/test/explorer/smart_contract/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/verifier_test.exs index 631c15695e..1e8301a90c 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/verifier_test.exs @@ -117,31 +117,6 @@ defmodule Explorer.SmartContract.VerifierTest do assert abi != nil end - test "returns error when constructor arguments do not match", %{ - contract_code_info: contract_code_info - } do - contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) - - constructor_arguments = "0102030405" - - params = %{ - "contract_source_code" => contract_code_info.source_code, - "compiler_version" => contract_code_info.version, - "name" => contract_code_info.name, - "optimization" => contract_code_info.optimized, - "constructor_arguments" => constructor_arguments - } - - :transaction - |> insert( - created_contract_address_hash: contract_address.hash, - input: Verifier.extract_bytecode(contract_code_info.bytecode) <> "010203" - ) - |> with_block() - - assert {:error, :constructor_arguments} = Verifier.evaluate_authenticity(contract_address.hash, params) - end - test "returns error when bytecode doesn't match", %{contract_code_info: contract_code_info} do contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)