Merge branch 'master' into vb-improve-stream_unfetched_token_instances-query

pull/2914/head
Victor Baranov 5 years ago committed by GitHub
commit 3098b30fc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 7
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  3. 19
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  4. 102
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  5. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  6. 210
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  7. 16
      apps/explorer/lib/explorer/token/instance_metadata_retriever.ex

@ -1,11 +1,13 @@
## Current
### Features
- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action
### Fixes
- [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query
- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache
- [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time
- [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places
### Chore
- [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests
@ -15,6 +17,7 @@
### Features
- [#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
- [#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

@ -589,6 +589,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("Some Token Name")
}
@token_id_type %{
type: "integer",
definition: "id of token",
example: ~s("0")
}
@token_symbol_type %{
type: "string",
definition: "Trading symbol of the token.",
@ -752,6 +758,7 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("663046792267785498951364")
},
tokenName: @token_name_type,
tokenID: @token_id_type,
tokenSymbol: @token_symbol_type,
tokenDecimal: @token_decimal_type,
transactionIndex: @transaction_index_type,

@ -121,7 +121,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
}
end
defp prepare_token_transfer(token_transfer) do
defp prepare_common_token_transfer(token_transfer) do
%{
"blockNumber" => to_string(token_transfer.block_number),
"timeStamp" => to_string(DateTime.to_unix(token_transfer.block_timestamp)),
@ -132,7 +132,6 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"contractAddress" => to_string(token_transfer.token_contract_address_hash),
"to" => to_string(token_transfer.to_address_hash),
"logIndex" => to_string(token_transfer.token_log_index),
"value" => get_token_value(token_transfer),
"tokenName" => token_transfer.token_name,
"tokenSymbol" => token_transfer.token_symbol,
"tokenDecimal" => to_string(token_transfer.token_decimals),
@ -146,12 +145,20 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
}
end
defp get_token_value(%{token_type: "ERC-721"} = token_transfer) do
to_string(token_transfer.token_id)
defp prepare_token_transfer(%{token_type: "ERC-721"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()
|> Map.put_new(:tokenID, token_transfer.token_id)
end
defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()
|> Map.put_new(:value, to_string(token_transfer.amount))
end
defp get_token_value(token_transfer) do
to_string(token_transfer.amount)
defp prepare_token_transfer(token_transfer) do
prepare_common_token_transfer(token_transfer)
end
defp prepare_block(block) do

@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
alias BlockScoutWeb.API.RPC.RPCView
alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
defguardp is_empty_string(input) when input == "" or input == nil
def render("listcontracts.json", %{contracts: contracts}) do
contracts = Enum.map(contracts, &prepare_contract/1)
@ -35,7 +37,11 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
"CompilerVersion" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => decompiler_version(nil),
"OptimizationUsed" => ""
"OptimizationUsed" => "",
"OptimizationRuns" => "",
"EVMVersion" => "",
"ConstructorArguments" => "",
"ExternalLibraries" => ""
}
end
@ -43,6 +49,68 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts)
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_constructor_arguments(contract_output, _), do: contract_output
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 =
if is_nil(address.smart_contract) do
"Contract source code not verified"
@ -51,27 +119,28 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end
contract_optimization =
case Map.get(contract, :optimization, "") do
case optimization do
true ->
"1"
"true"
false ->
"0"
"false"
"" ->
""
end
%{
"Address" => to_string(address.hash),
"SourceCode" => Map.get(contract, :contract_source_code, ""),
"ABI" => contract_abi,
"ContractName" => Map.get(contract, :name, ""),
"DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => Map.get(contract, :compiler_version, ""),
"OptimizationUsed" => contract_optimization
}
if Map.equal?(contract, %{}) do
contract_output
else
contract_output
|> Map.put_new(:SourceCode, Map.get(contract, :contract_source_code, ""))
|> Map.put_new(:ABI, contract_abi)
|> Map.put_new(:ContractName, Map.get(contract, :name, ""))
|> Map.put_new(:CompilerVersion, Map.get(contract, :compiler_version, ""))
|> Map.put_new(:OptimizationUsed, contract_optimization)
|> Map.put_new(:EVMVersion, Map.get(contract, :evm_version, ""))
end
end
defp prepare_contract(%Address{
@ -80,10 +149,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
}) do
%{
"Address" => to_string(hash),
"ABI" => "Contract source code not verified",
"ContractName" => "",
"CompilerVersion" => "",
"OptimizationUsed" => ""
"ABI" => "Contract source code not verified"
}
end

@ -1807,7 +1807,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> get("/api", params)
|> json_response(200)
assert result["value"] == to_string(token_transfer.token_id)
assert result["tokenID"] == to_string(token_transfer.token_id)
assert response["status"] == "1"
assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response)
@ -2618,6 +2618,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"logIndex" => %{"type" => "string"},
"value" => %{"type" => "string"},
"tokenName" => %{"type" => "string"},
"tokenID" => %{"type" => "string"},
"tokenSymbol" => %{"type" => "string"},
"tokenDecimal" => %{"type" => "string"},
"transactionIndex" => %{"type" => "string"},

@ -1,5 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.SmartContract
alias Explorer.{Chain, Factory}
describe "listcontracts" do
@ -70,10 +71,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(address.hash)
}
]
@ -95,10 +93,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(address.hash)
}
]
@ -124,10 +119,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(address.hash)
}
]
@ -174,10 +166,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(decompiled_smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(decompiled_smart_contract.address_hash)
}
]
@ -199,10 +188,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(smart_contract.address_hash)
}
]
@ -225,10 +211,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert %{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(smart_contract.address_hash)
} in response["result"]
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"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(contract_address.hash)
}
]
@ -281,10 +261,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"OptimizationUsed" => ""
"Address" => to_string(contract_address.hash)
}
]
@ -423,7 +400,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"CompilerVersion" => "",
"OptimizationUsed" => "",
"DecompiledSourceCode" => "",
"DecompilerVersion" => ""
"DecompilerVersion" => "",
"ConstructorArguments" => "",
"EVMVersion" => "",
"ExternalLibraries" => "",
"OptimizationRuns" => ""
}
]
@ -439,7 +420,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
end
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 = %{
"module" => "contract",
@ -456,12 +437,156 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
# The contract's optimization value is true, so the expected value
# for `OptimizationUsed` is "1". If it was false, the expected value
# would be "0".
"DecompilerVersion" => "",
"OptimizationUsed" => "1"
"OptimizationUsed" => "true",
"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),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "0"
"OptimizationUsed" => "false",
"EVMVersion" => nil
}
assert response["status"] == "1"
@ -578,9 +702,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""
assert result["OptimizationUsed"] == "1"
assert result["DecompiledSourceCode"] == nil
assert result["DecompilerVersion"] == nil
assert result["OptimizationUsed"] == "true"
assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response)
end
end

@ -80,7 +80,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
def fetch_json(%{"tokenURI" => {:ok, [json]}}) do
{:ok, json} = decode_json(json)
{:ok, %{metadata: json}}
check_type(json)
rescue
e ->
Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"],
@ -101,11 +101,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
{:ok, %Response{body: body, status_code: 200}} ->
{:ok, json} = decode_json(body)
if is_map(json) do
{:ok, %{metadata: json}}
else
{:error, :wrong_metadata_type}
end
check_type(json)
{:ok, %Response{body: body}} ->
{:error, body}
@ -131,4 +127,12 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
|> Jason.decode()
end
end
defp check_type(json) when is_map(json) do
{:ok, %{metadata: json}}
end
defp check_type(_) do
{:error, :wrong_metadata_type}
end
end

Loading…
Cancel
Save