Merge branch 'master' into ab-use-seconds-to-update-transaction-cache

pull/1699/head
Ayrat Badykov 6 years ago committed by GitHub
commit 34bd1a5be9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 85
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  3. 166
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  4. 4
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  5. 97
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  6. 13
      apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json

@ -5,6 +5,7 @@
- [#1662](https://github.com/poanetwork/blockscout/pull/1662) - allow specifying number of optimization runs - [#1662](https://github.com/poanetwork/blockscout/pull/1662) - allow specifying number of optimization runs
- [#1654](https://github.com/poanetwork/blockscout/pull/1654) - add decompiled code tab - [#1654](https://github.com/poanetwork/blockscout/pull/1654) - add decompiled code tab
- [#1661](https://github.com/poanetwork/blockscout/pull/1661) - try to compile smart contract with the latest evm version - [#1661](https://github.com/poanetwork/blockscout/pull/1661) - try to compile smart contract with the latest evm version
- [#1665](https://github.com/poanetwork/blockscout/pull/1665) - Add contract verification RPC endpoint.
### Fixes ### Fixes

@ -4,6 +4,24 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
alias BlockScoutWeb.API.RPC.Helpers alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.SmartContract alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Publisher
def verify(conn, %{"addressHash" => address_hash} = params) do
with {:params, {:ok, fetched_params}} <- {:params, fetch_verify_params(params)},
{:params, external_libraries} <-
{:params, fetch_external_libraries(params)},
{:publish, {:ok, smart_contract}} <-
{:publish, Publisher.publish(address_hash, fetched_params, external_libraries)},
preloaded_smart_contract <- SmartContract.preload_decompiled_smart_contract(smart_contract) do
render(conn, :verify, %{contract: preloaded_smart_contract, address_hash: address_hash})
else
{:publish, _} ->
render(conn, :error, error: "Something went wrong while publishing the contract.")
{:params, {:error, error}} ->
render(conn, :error, error: error)
end
end
def listcontracts(conn, params) do def listcontracts(conn, params) do
with pagination_options <- Helpers.put_pagination_options(%{}, params), with pagination_options <- Helpers.put_pagination_options(%{}, params),
@ -155,4 +173,71 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
{:contract, result} {:contract, result}
end end
defp fetch_verify_params(params) do
{:ok, %{}}
|> required_param(params, "addressHash", "address_hash")
|> required_param(params, "name", "name")
|> required_param(params, "compilerVersion", "compiler_version")
|> required_param(params, "optimization", "optimization")
|> required_param(params, "contractSourceCode", "contract_source_code")
|> optional_param(params, "evmVersion", "evm_version")
|> optional_param(params, "constructorArguments", "constructor_arguments")
|> optional_param(params, "optimizationRuns", "optimization_runs")
|> parse_optimization_runs()
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_integer(runs) do
{:ok, opts}
end
defp parse_optimization_runs({:ok, opts}) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs(other), do: other
defp fetch_external_libraries(params) do
Enum.reduce(1..5, %{}, fn number, acc ->
case Map.fetch(params, "library#{number}Name") do
{:ok, library_name} ->
library_address = Map.get(params, "library#{number}Address")
acc
|> Map.put("library#{number}_name", library_name)
|> Map.put("library#{number}_address", library_address)
:error ->
acc
end
end)
end
defp required_param({:error, _} = error, _, _, _), do: error
defp required_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:error, "#{key} is required."}
end
end
defp optional_param({:error, _} = error, _, _, _), do: error
defp optional_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:ok, map}
end
end
end end

@ -328,6 +328,49 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "result" => nil
} }
@contract_verify_example_value %{
"status" => "1",
"message" => "OK",
"result" => %{
"SourceCode" => """
pragma solidity >0.4.24;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
""",
"ABI" => """
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
""",
"ContractName" => "Test",
"CompilerVersion" => "v0.2.1-2016-01-30-91a6b35",
"OptimizationUsed" => "1"
}
}
@contract_verify_example_value_error %{
"status" => "0",
"message" => "There was an error verifying the contract.",
"result" => nil
}
@contract_getsourcecode_example_value %{ @contract_getsourcecode_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -1747,6 +1790,126 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@contract_verify_action %{
name: "verify",
description: "Verify a contract with its source code and contract creation information.",
required_params: [
%{
key: "addressHash",
placeholder: "addressHash",
type: "string",
description: "The address of the contract."
},
%{
key: "name",
placeholder: "name",
type: "string",
description: "The name of the contract."
},
%{
key: "compilerVersion",
placeholder: "compilerVersion",
type: "string",
description: "The compiler version for the contract."
},
%{
key: "optimization",
placeholder: false,
type: "boolean",
description: "Whether or not compiler optimizations were enabled."
},
%{
key: "contractSourceCode",
placeholder: "contractSourceCode",
type: "string",
description: "The source code of the contract."
}
],
optional_params: [
%{
key: "constructorArguments",
type: "string",
description: "The constructor argument data provided."
},
%{
key: "evmVersion",
placeholder: "evmVersion",
type: "string",
description: "The EVM version for the contract."
},
%{
key: "optimizationRuns",
placeholder: "optimizationRuns",
type: "integer",
description: "The number of optimization runs used during compilation"
},
%{
key: "library1Name",
type: "string",
description: "The name of the first library used."
},
%{
key: "library1Address",
type: "string",
description: "The address of the first library used."
},
%{
key: "library2Name",
type: "string",
description: "The name of the second library used."
},
%{
key: "library2Address",
type: "string",
description: "The address of the second library used."
},
%{
key: "library3Name",
type: "string",
description: "The name of the third library used."
},
%{
key: "library3Address",
type: "string",
description: "The address of the third library used."
},
%{
key: "library4Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library4Address",
type: "string",
description: "The address of the fourth library used."
},
%{
key: "library5Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library5Address",
type: "string",
description: "The address of the fourth library used."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_verify_example_value),
type: "model",
model: @contract_model
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@contract_verify_example_value_error)
}
]
}
@contract_getabi_action %{ @contract_getabi_action %{
name: "getabi", name: "getabi",
description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.",
@ -1976,7 +2139,8 @@ defmodule BlockScoutWeb.Etherscan do
actions: [ actions: [
@contract_listcontracts_action, @contract_listcontracts_action,
@contract_getabi_action, @contract_getabi_action,
@contract_getsourcecode_action @contract_getsourcecode_action,
@contract_verify_action
] ]
} }

@ -22,6 +22,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end
def render("verify.json", %{contract: contract, address_hash: address_hash}) do
RPCView.render("show.json", data: prepare_source_code_contract(contract, address_hash))
end
defp prepare_source_code_contract(nil, address_hash) do defp prepare_source_code_contract(nil, address_hash) do
%{ %{
"Address" => to_string(address_hash), "Address" => to_string(address_hash),

@ -1,5 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
alias Explorer.Factory
describe "listcontracts" do describe "listcontracts" do
setup do setup do
@ -413,4 +414,100 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
end end
describe "verify" do
test "with an address that doesn't exist", %{conn: conn} do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => contract_code_info.name,
"compilerVersion" => contract_code_info.version,
"optimization" => contract_code_info.optimized,
"contractSourceCode" => contract_code_info.source_code
}
response =
conn
|> get("/api", params)
|> json_response(200)
expected_result = %{
"Address" => to_string(contract_address.hash),
"SourceCode" => contract_code_info.source_code,
"ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "0"
}
assert response["status"] == "1"
assert response["result"] == expected_result
assert response["message"] == "OK"
end
test "with external libraries", %{conn: conn} do
contract_data =
"#{File.cwd!()}/test/support/fixture/smart_contract/compiler_tests.json"
|> File.read!()
|> Jason.decode!()
|> List.first()
%{
"compiler_version" => compiler_version,
"external_libraries" => external_libraries,
"name" => name,
"optimize" => optimize,
"contract" => contract_source_code,
"expected_bytecode" => expected_bytecode
} = contract_data
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => name,
"compilerVersion" => compiler_version,
"optimization" => optimize,
"contractSourceCode" => contract_source_code
}
params_with_external_libraries =
external_libraries
|> Enum.with_index()
|> Enum.reduce(params, fn {{name, address}, index}, acc ->
name_key = "library#{index + 1}Name"
address_key = "library#{index + 1}Address"
acc
|> Map.put(name_key, name)
|> Map.put(address_key, address)
end)
response =
conn
|> get("/api", params_with_external_libraries)
|> json_response(200)
assert response["status"] == "1"
assert response["message"] == "OK"
result = response["result"]
assert result["Address"] == to_string(contract_address.hash)
assert result["SourceCode"] == contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""
assert result["OptimizationUsed"] == "1"
end
end
end end

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