Merge pull request #2857 from poanetwork/vb-extend-contract-api-view

Extend getsourcecode API view with new output fields
pull/2917/head
Victor Baranov 5 years ago committed by GitHub
commit a443aa9eca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 100
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  3. 210
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs

@ -16,6 +16,7 @@
### Features ### Features
- [#2862](https://github.com/poanetwork/blockscout/pull/2862) - Coin total supply from DB API endpoint - [#2862](https://github.com/poanetwork/blockscout/pull/2862) - Coin total supply from DB API endpoint
- [#2857](https://github.com/poanetwork/blockscout/pull/2857) - Extend getsourcecode API view with new output fields
- [#2822](https://github.com/poanetwork/blockscout/pull/2822) - Estimated address count on the main page, if cache is empty - [#2822](https://github.com/poanetwork/blockscout/pull/2822) - Estimated address count on the main page, if cache is empty
- [#2821](https://github.com/poanetwork/blockscout/pull/2821) - add autodetection of constructor arguments - [#2821](https://github.com/poanetwork/blockscout/pull/2821) - add autodetection of constructor arguments
- [#2825](https://github.com/poanetwork/blockscout/pull/2825) - separate token transfers and transactions - [#2825](https://github.com/poanetwork/blockscout/pull/2825) - separate token transfers and transactions

@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.RPCView
alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract} alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
defguardp is_empty_string(input) when input == "" or input == nil
def render("listcontracts.json", %{contracts: contracts}) do def render("listcontracts.json", %{contracts: contracts}) do
contracts = Enum.map(contracts, &prepare_contract/1) contracts = Enum.map(contracts, &prepare_contract/1)
@ -35,7 +37,11 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
"CompilerVersion" => "", "CompilerVersion" => "",
"DecompiledSourceCode" => "", "DecompiledSourceCode" => "",
"DecompilerVersion" => decompiler_version(nil), "DecompilerVersion" => decompiler_version(nil),
"OptimizationUsed" => "" "OptimizationUsed" => "",
"OptimizationRuns" => "",
"EVMVersion" => "",
"ConstructorArguments" => "",
"ExternalLibraries" => ""
} }
end end
@ -43,6 +49,66 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts) decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts)
contract = address.smart_contract || %{} contract = address.smart_contract || %{}
optimization = Map.get(contract, :optimization, "")
contract_output = %{
"Address" => to_string(address.hash)
}
contract_output
|> set_decompiled_contract_data(decompiled_smart_contract)
|> set_optimization_runs(contract, optimization)
|> set_constructor_arguments(contract)
|> set_external_libraries(contract)
|> set_verified_contract_data(contract, address, optimization)
end
defp set_decompiled_contract_data(contract_output, decompiled_smart_contract) do
if decompiled_smart_contract do
contract_output
|> Map.put_new(:DecompiledSourceCode, decompiled_source_code(decompiled_smart_contract))
|> Map.put_new(:DecompilerVersion, decompiler_version(decompiled_smart_contract))
else
contract_output
end
end
defp set_optimization_runs(contract_output, contract, optimization) do
optimization_runs = Map.get(contract, :optimization_runs, "")
if optimization && optimization != "" do
contract_output
|> Map.put_new(:OptimizationRuns, optimization_runs)
else
contract_output
end
end
defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) when is_empty_string(arguments),
do: contract_output
defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) do
contract_output
|> Map.put_new(:ConstructorArguments, arguments)
end
defp set_external_libraries(contract_output, contract) do
external_libraries = Map.get(contract, :external_libraries, [])
if Enum.count(external_libraries) > 0 do
external_libraries_without_id =
Enum.map(external_libraries, fn %{name: name, address_hash: address_hash} ->
%{"name" => name, "address_hash" => address_hash}
end)
contract_output
|> Map.put_new(:ExternalLibraries, external_libraries_without_id)
else
contract_output
end
end
defp set_verified_contract_data(contract_output, contract, address, optimization) do
contract_abi = contract_abi =
if is_nil(address.smart_contract) do if is_nil(address.smart_contract) do
"Contract source code not verified" "Contract source code not verified"
@ -51,27 +117,28 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end end
contract_optimization = contract_optimization =
case Map.get(contract, :optimization, "") do case optimization do
true -> true ->
"1" "true"
false -> false ->
"0" "false"
"" -> "" ->
"" ""
end end
%{ if Map.equal?(contract, %{}) do
"Address" => to_string(address.hash), contract_output
"SourceCode" => Map.get(contract, :contract_source_code, ""), else
"ABI" => contract_abi, contract_output
"ContractName" => Map.get(contract, :name, ""), |> Map.put_new(:SourceCode, Map.get(contract, :contract_source_code, ""))
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract), |> Map.put_new(:ABI, contract_abi)
"DecompilerVersion" => decompiler_version(decompiled_smart_contract), |> Map.put_new(:ContractName, Map.get(contract, :name, ""))
"CompilerVersion" => Map.get(contract, :compiler_version, ""), |> Map.put_new(:CompilerVersion, Map.get(contract, :compiler_version, ""))
"OptimizationUsed" => contract_optimization |> Map.put_new(:OptimizationUsed, contract_optimization)
} |> Map.put_new(:EVMVersion, Map.get(contract, :evm_version, ""))
end
end end
defp prepare_contract(%Address{ defp prepare_contract(%Address{
@ -80,10 +147,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
}) do }) do
%{ %{
"Address" => to_string(hash), "Address" => to_string(hash),
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified"
"ContractName" => "",
"CompilerVersion" => "",
"OptimizationUsed" => ""
} }
end end

@ -1,5 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
alias Explorer.Chain.SmartContract
alias Explorer.{Chain, Factory} alias Explorer.{Chain, Factory}
describe "listcontracts" do describe "listcontracts" do
@ -70,10 +71,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(address.hash), "Address" => to_string(address.hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -95,10 +93,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(address.hash), "Address" => to_string(address.hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -124,10 +119,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(address.hash), "Address" => to_string(address.hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -174,10 +166,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(decompiled_smart_contract.address_hash), "Address" => to_string(decompiled_smart_contract.address_hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -199,10 +188,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash), "Address" => to_string(smart_contract.address_hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -225,10 +211,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert %{ assert %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash), "Address" => to_string(smart_contract.address_hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} in response["result"] } in response["result"]
refute to_string(non_match.address_hash) in Enum.map(response["result"], &Map.get(&1, "Address")) refute to_string(non_match.address_hash) in Enum.map(response["result"], &Map.get(&1, "Address"))
@ -251,10 +234,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash), "Address" => to_string(contract_address.hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -281,10 +261,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [ assert response["result"] == [
%{ %{
"ABI" => "Contract source code not verified", "ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash), "Address" => to_string(contract_address.hash)
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
} }
] ]
@ -423,7 +400,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"CompilerVersion" => "", "CompilerVersion" => "",
"OptimizationUsed" => "", "OptimizationUsed" => "",
"DecompiledSourceCode" => "", "DecompiledSourceCode" => "",
"DecompilerVersion" => "" "DecompilerVersion" => "",
"ConstructorArguments" => "",
"EVMVersion" => "",
"ExternalLibraries" => "",
"OptimizationRuns" => ""
} }
] ]
@ -439,7 +420,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
end end
test "with a verified contract address", %{conn: conn} do test "with a verified contract address", %{conn: conn} do
contract = insert(:smart_contract, optimization: true) contract = insert(:smart_contract, optimization: true, optimization_runs: 200, evm_version: "default")
params = %{ params = %{
"module" => "contract", "module" => "contract",
@ -456,12 +437,156 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"ABI" => Jason.encode!(contract.abi), "ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name, "ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version, "CompilerVersion" => contract.compiler_version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
# The contract's optimization value is true, so the expected value # The contract's optimization value is true, so the expected value
# for `OptimizationUsed` is "1". If it was false, the expected value # for `OptimizationUsed` is "1". If it was false, the expected value
# would be "0". # would be "0".
"DecompilerVersion" => "", "OptimizationUsed" => "true",
"OptimizationUsed" => "1" "OptimizationRuns" => 200,
"EVMVersion" => "default"
}
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)
end
test "with constructor arguments", %{conn: conn} do
contract =
insert(:smart_contract,
optimization: true,
optimization_runs: 200,
evm_version: "default",
constructor_arguments:
"00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546"
)
params = %{
"module" => "contract",
"action" => "getsourcecode",
"address" => to_string(contract.address_hash)
}
expected_result = [
%{
"Address" => to_string(contract.address_hash),
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <>
contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => "true",
"OptimizationRuns" => 200,
"EVMVersion" => "default",
"ConstructorArguments" =>
"00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546"
}
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response)
end
test "with external library", %{conn: conn} do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "Test",
compiler_version: "0.4.23",
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
optimization: true,
optimization_runs: 200,
evm_version: "default"
}
external_libraries = [
%SmartContract.ExternalLibrary{:address_hash => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95", :name => "Test"},
%SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"}
]
{:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries)
params = %{
"module" => "contract",
"action" => "getsourcecode",
"address" => to_string(contract.address_hash)
}
expected_result = [
%{
"Address" => to_string(contract.address_hash),
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <>
contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => "true",
"OptimizationRuns" => 200,
"EVMVersion" => "default",
"ExternalLibraries" => [
%{"name" => "Test", "address_hash" => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95"},
%{"name" => "Test2", "address_hash" => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f"}
]
} }
] ]
@ -508,9 +633,8 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"ABI" => Jason.encode!(contract_code_info.abi), "ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name, "ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version, "CompilerVersion" => contract_code_info.version,
"DecompiledSourceCode" => "Contract source code not decompiled.", "OptimizationUsed" => "false",
"DecompilerVersion" => "", "EVMVersion" => nil
"OptimizationUsed" => "0"
} }
assert response["status"] == "1" assert response["status"] == "1"
@ -578,9 +702,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
contract_source_code contract_source_code
assert result["ContractName"] == name assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled." assert result["DecompiledSourceCode"] == nil
assert result["DecompilerVersion"] == "" assert result["DecompilerVersion"] == nil
assert result["OptimizationUsed"] == "1" assert result["OptimizationUsed"] == "true"
assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response) assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response)
end end
end end

Loading…
Cancel
Save