Merge branch 'master' into ab-fix-empty-buffered-task

pull/2899/head
Victor Baranov 5 years ago committed by GitHub
commit 5c051d26dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      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. 18
      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. 2
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs
  8. 22
      apps/explorer/lib/explorer/chain.ex
  9. 2
      apps/explorer/lib/explorer/chain/cache/address_sum.ex
  10. 25
      apps/explorer/lib/explorer/counters/average_block_time.ex
  11. 16
      apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
  12. 2
      apps/explorer/test/explorer/chain/cache/address_sum_test.exs
  13. 42
      apps/explorer/test/explorer/counters/average_block_time_test.exs
  14. 44
      apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs

@ -1,12 +1,16 @@
## Current ## Current
### Features ### Features
- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action
### Fixes ### 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
- [#2899](https://github.com/poanetwork/blockscout/pull/2899) - fix empty buffered task - [#2899](https://github.com/poanetwork/blockscout/pull/2899) - fix empty buffered task
### Chore ### Chore
- [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests
@ -14,6 +18,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

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

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

@ -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,68 @@ 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_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 = 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 +119,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 +149,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

@ -88,21 +88,24 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
mining_address = mining_address =
insert(:address, insert(:address,
fetched_coin_balance: 0, fetched_coin_balance: 0,
fetched_coin_balance_block_number: 2, fetched_coin_balance_block_number: 102,
inserted_at: Timex.shift(now, minutes: -10) inserted_at: Timex.shift(now, minutes: -10)
) )
mining_address_hash = to_string(mining_address.hash) mining_address_hash = to_string(mining_address.hash)
# we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far
# back we'd need to go to get 24 hours in the past) # back we'd need to go to get 24 hours in the past)
insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50), miner: mining_address) Enum.each(0..100, fn i ->
insert(:block, number: 1, timestamp: Timex.shift(now, hours: -25), miner: mining_address) insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 25), miner: mining_address)
end)
insert(:block, number: 101, timestamp: Timex.shift(now, hours: -25), miner: mining_address)
AverageBlockTime.refresh() AverageBlockTime.refresh()
address = address =
insert(:address, insert(:address,
fetched_coin_balance: 100, fetched_coin_balance: 100,
fetched_coin_balance_block_number: 0, fetched_coin_balance_block_number: 100,
inserted_at: Timex.shift(now, minutes: -5) inserted_at: Timex.shift(now, minutes: -5)
) )
@ -112,7 +115,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
%{ %{
id: id, id: id,
method: "eth_getBalance", method: "eth_getBalance",
params: [^address_hash, "0x1"] params: [^address_hash, "0x65"]
} }
], ],
_options -> _options ->
@ -148,7 +151,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert received_address.hash == address.hash assert received_address.hash == address.hash
assert received_address.fetched_coin_balance == expected_wei assert received_address.fetched_coin_balance == expected_wei
assert received_address.fetched_coin_balance_block_number == 1 assert received_address.fetched_coin_balance_block_number == 101
end end
end end
@ -1804,7 +1807,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> get("/api", params) |> get("/api", params)
|> json_response(200) |> 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["status"] == "1"
assert response["message"] == "OK" assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response)
@ -2615,6 +2618,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"logIndex" => %{"type" => "string"}, "logIndex" => %{"type" => "string"},
"value" => %{"type" => "string"}, "value" => %{"type" => "string"},
"tokenName" => %{"type" => "string"}, "tokenName" => %{"type" => "string"},
"tokenID" => %{"type" => "string"},
"tokenSymbol" => %{"type" => "string"}, "tokenSymbol" => %{"type" => "string"},
"tokenDecimal" => %{"type" => "string"}, "tokenDecimal" => %{"type" => "string"},
"transactionIndex" => %{"type" => "string"}, "transactionIndex" => %{"type" => "string"},

@ -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

@ -116,7 +116,7 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do
|> get("/api", params) |> get("/api", params)
|> json_response(200) |> json_response(200)
assert response["result"] == "6" assert response["result"] == "0"
assert response["status"] == "1" assert response["status"] == "1"
assert response["message"] == "OK" assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(ethsupply_schema(), response) assert :ok = ExJsonSchema.Validator.validate(ethsupply_schema(), response)

@ -2922,7 +2922,7 @@ defmodule Explorer.Chain do
""" """
@spec total_supply :: non_neg_integer() | nil @spec total_supply :: non_neg_integer() | nil
def total_supply do def total_supply do
supply_module().total() supply_module().total() || 0
end end
@doc """ @doc """
@ -2967,21 +2967,33 @@ defmodule Explorer.Chain do
) :: {:ok, accumulator} ) :: {:ok, accumulator}
when accumulator: term() when accumulator: term()
def stream_unfetched_token_instances(initial, reducer) when is_function(reducer, 2) do def stream_unfetched_token_instances(initial, reducer) when is_function(reducer, 2) do
nft_tokens =
from(
token in Token,
where: token.type == ^"ERC-721",
select: token.contract_address_hash
)
query = query =
from( from(
token_transfer in TokenTransfer, token_transfer in TokenTransfer,
inner_join: token in Token, inner_join: token in subquery(nft_tokens),
on: token.contract_address_hash == token_transfer.token_contract_address_hash, on: token.contract_address_hash == token_transfer.token_contract_address_hash,
left_join: instance in Instance, left_join: instance in Instance,
on: on:
token_transfer.token_id == instance.token_id and token_transfer.token_id == instance.token_id and
token_transfer.token_contract_address_hash == instance.token_contract_address_hash, token_transfer.token_contract_address_hash == instance.token_contract_address_hash,
where: token.type == ^"ERC-721" and is_nil(instance.token_id) and not is_nil(token_transfer.token_id), where: is_nil(instance.token_id) and not is_nil(token_transfer.token_id),
distinct: [token_transfer.token_contract_address_hash, token_transfer.token_id],
select: %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id} select: %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id}
) )
Repo.stream_reduce(query, initial, reducer) distinct_query =
from(
q in subquery(query),
distinct: [q.contract_address_hash, q.token_id]
)
Repo.stream_reduce(distinct_query, initial, reducer)
end end
@doc """ @doc """

@ -20,7 +20,7 @@ defmodule Explorer.Chain.Cache.AddressSum do
# See next `handle_fallback` definition # See next `handle_fallback` definition
get_async_task() get_async_task()
{:return, nil} {:return, Decimal.new(0)}
end end
defp handle_fallback(:async_task) do defp handle_fallback(:async_task) do

@ -63,24 +63,25 @@ defmodule Explorer.Counters.AverageBlockTime do
defp refresh_timestamps do defp refresh_timestamps do
timestamps_query = timestamps_query =
from(block in Block,
limit: 100,
offset: 0,
order_by: [desc: block.number],
select: {block.number, block.timestamp}
)
query =
if Application.get_env(:explorer, :include_uncles_in_average_block_time) do if Application.get_env(:explorer, :include_uncles_in_average_block_time) do
timestamps_query from(block in Block,
limit: 100,
offset: 100,
order_by: [desc: block.number],
select: {block.number, block.timestamp}
)
else else
from(block in timestamps_query, from(block in Block,
where: block.consensus == true limit: 100,
offset: 100,
order_by: [desc: block.number],
where: block.consensus == true,
select: {block.number, block.timestamp}
) )
end end
timestamps = timestamps =
query timestamps_query
|> Repo.all() |> Repo.all()
|> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2) |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2)
|> Enum.map(fn {number, timestamp} -> |> Enum.map(fn {number, timestamp} ->

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

@ -12,7 +12,7 @@ defmodule Explorer.Chain.Cache.AddressSumTest do
test "returns default address sum" do test "returns default address sum" do
result = AddressSum.get_sum() result = AddressSum.get_sum()
assert is_nil(result) assert result == Decimal.new(0)
end end
test "updates cache if initial value is zero" do test "updates cache if initial value is zero" do

@ -34,11 +34,19 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
first_timestamp = Timex.now() first_timestamp = Timex.now()
insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3)) insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -100 - 3))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 9)) insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: -100 - 9))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 6)) insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: -100 - 6))
Enum.each(1..100, fn i ->
insert(:block,
number: block_number + i,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9)
)
end)
assert Repo.aggregate(Block, :count, :hash) == 3 assert Repo.aggregate(Block, :count, :hash) == 103
AverageBlockTime.refresh() AverageBlockTime.refresh()
@ -55,6 +63,14 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4)) insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5))
Enum.each(1..100, fn i ->
insert(:block,
number: block_number + i + 1,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 5)
)
end)
AverageBlockTime.refresh() AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT2S") assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT2S")
@ -69,6 +85,14 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4)) insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5))
Enum.each(1..100, fn i ->
insert(:block,
number: block_number + i + 1,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 5)
)
end)
AverageBlockTime.refresh() AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S") assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S")
@ -83,7 +107,15 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9)) insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6))
assert Repo.aggregate(Block, :count, :hash) == 3 Enum.each(1..100, fn i ->
insert(:block,
number: block_number + i + 2,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9)
)
end)
assert Repo.aggregate(Block, :count, :hash) == 103
AverageBlockTime.refresh() AverageBlockTime.refresh()

@ -41,15 +41,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
# we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far
# back we'd need to go to get 24 hours in the past) # back we'd need to go to get 24 hours in the past)
insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50)) Enum.each(0..100, fn i ->
insert(:block, number: 1, timestamp: now) insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50))
end)
insert(:block, number: 101, timestamp: now)
AverageBlockTime.refresh() AverageBlockTime.refresh()
stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 0) stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100)
current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 1) current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101)
pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 1) pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101)
insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 2) insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 102)
%{stale_address: stale_address, current_address: current_address, pending_address: pending_address} %{stale_address: stale_address, current_address: current_address, pending_address: pending_address}
end end
@ -63,7 +66,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
test "if the address has not been fetched within the last 24 hours of blocks it is considered stale", %{ test "if the address has not been fetched within the last 24 hours of blocks it is considered stale", %{
stale_address: address stale_address: address
} do } do
assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 1} assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101}
end end
test "if the address has been fetched within the last 24 hours of blocks it is considered current", %{ test "if the address has been fetched within the last 24 hours of blocks it is considered current", %{
@ -75,7 +78,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
test "if there is an unfetched balance within the window for an address, it is considered pending", %{ test "if there is an unfetched balance within the window for an address, it is considered pending", %{
pending_address: pending_address pending_address: pending_address
} do } do
assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 2} assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 102}
end end
end end
@ -88,15 +91,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
# we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far
# back we'd need to go to get 24 hours in the past) # back we'd need to go to get 24 hours in the past)
insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50)) Enum.each(0..100, fn i ->
insert(:block, number: 1, timestamp: now) insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50))
end)
insert(:block, number: 101, timestamp: now)
AverageBlockTime.refresh() AverageBlockTime.refresh()
:ok :ok
end end
test "a stale address broadcasts the new address" do test "a stale address broadcasts the new address" do
address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 0) address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100)
address_hash = address.hash address_hash = address.hash
string_address_hash = to_string(address.hash) string_address_hash = to_string(address.hash)
@ -104,26 +110,26 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
%{ %{
id: id, id: id,
method: "eth_getBalance", method: "eth_getBalance",
params: [^string_address_hash, "0x1"] params: [^string_address_hash, "0x65"]
} }
], ],
_options -> _options ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end) end)
assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 1} assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101}
{:ok, expected_wei} = Wei.cast(2) {:ok, expected_wei} = Wei.cast(2)
assert_receive( assert_receive(
{:chain_event, :addresses, :on_demand, {:chain_event, :addresses, :on_demand,
[%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 1}]} [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 101}]}
) )
end end
test "a pending address broadcasts the new address and the new coin balance" do test "a pending address broadcasts the new address and the new coin balance" do
address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 1) address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 101)
insert(:unfetched_balance, address_hash: address.hash, block_number: 2) insert(:unfetched_balance, address_hash: address.hash, block_number: 102)
address_hash = address.hash address_hash = address.hash
string_address_hash = to_string(address.hash) string_address_hash = to_string(address.hash)
@ -131,20 +137,20 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do
%{ %{
id: id, id: id,
method: "eth_getBalance", method: "eth_getBalance",
params: [^string_address_hash, "0x2"] params: [^string_address_hash, "0x66"]
} }
], ],
_options -> _options ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]}
end) end)
assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 2} assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 102}
{:ok, expected_wei} = Wei.cast(2) {:ok, expected_wei} = Wei.cast(2)
assert_receive( assert_receive(
{:chain_event, :addresses, :on_demand, {:chain_event, :addresses, :on_demand,
[%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 2}]} [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 102}]}
) )
end end
end end

Loading…
Cancel
Save