diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c748863ff..a046a1a134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,18 @@ ### Features - - [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 + - [#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 + - [#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 - [#1669](https://github.com/poanetwork/blockscout/pull/1669) - do not fail if multiple matching tokens are found + - [#1691](https://github.com/poanetwork/blockscout/pull/1691) - decrease token metadata update interval + - [#1688](https://github.com/poanetwork/blockscout/pull/1688) - do not fail if failure reason is atom + - [#1692](https://github.com/poanetwork/blockscout/pull/1692) - exclude decompiled smart contract from encoding + - [#1684](https://github.com/poanetwork/blockscout/pull/1684) - Discard child block with parent_hash not matching hash of imported block ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex index 75cda8c965..8f35dcaa86 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -26,10 +26,14 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "address_id" => address_hash_string, "smart_contract" => smart_contract, "external_libraries" => external_libraries, - "evm_version" => evm_version + "evm_version" => evm_version, + "optimization" => optimization } ) do - smart_sontact_with_evm_version = Map.put(smart_contract, "evm_version", evm_version["evm_version"]) + smart_sontact_with_evm_version = + smart_contract + |> Map.put("evm_version", evm_version["evm_version"]) + |> Map.put("optimization_runs", parse_optimization_runs(optimization)) case Publisher.publish(address_hash_string, smart_sontact_with_evm_version, external_libraries) do {:ok, _smart_contract} -> @@ -45,4 +49,11 @@ defmodule BlockScoutWeb.AddressContractVerificationController do ) end end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 8a8036f2b4..146626cce5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -4,10 +4,28 @@ defmodule BlockScoutWeb.API.RPC.ContractController do alias BlockScoutWeb.API.RPC.Helpers alias Explorer.Chain 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 with pagination_options <- Helpers.put_pagination_options(%{}, params), - {:params, {:ok, options}} <- {:params, add_filter(pagination_options, params)} do + {:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do options_with_defaults = options |> Map.put_new(:page_number, 0) @@ -71,7 +89,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do Chain.list_verified_contracts(page_size, offset) :decompiled -> - Chain.list_decompiled_contracts(page_size, offset) + not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version) + Chain.list_decompiled_contracts(page_size, offset, not_decompiled_with_version) :unverified -> Chain.list_unverified_contracts(page_size, offset) @@ -84,6 +103,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end + defp add_filters(options, params) do + options + |> add_filter(params) + |> add_not_decompiled_with_version(params) + end + defp add_filter(options, params) do with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")}, {:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do @@ -94,6 +119,17 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end + defp add_not_decompiled_with_version({:ok, options}, params) do + case Map.fetch(params, "not_decompiled_with_version") do + {:ok, value} -> {:ok, Map.put(options, :not_decompiled_with_version, value)} + :error -> {:ok, options} + end + end + + defp add_not_decompiled_with_version(options, _params) do + options + end + defp contracts_filter(nil), do: {:ok, nil} defp contracts_filter(1), do: {:ok, :verified} defp contracts_filter(2), do: {:ok, :decompiled} @@ -137,4 +173,71 @@ defmodule BlockScoutWeb.API.RPC.ContractController do {:contract, result} 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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex index 75bb51fa50..b1db31a8f3 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex @@ -66,6 +66,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do def call_controller(conn, controller, action) do {:ok, controller.call(conn, action)} rescue - Conn.WrapperError -> :error + Conn.WrapperError -> + :error end end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 4162a77e70..d68f0c95c7 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -328,6 +328,49 @@ defmodule BlockScoutWeb.Etherscan do "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 %{ "status" => "1", "message" => "OK", @@ -1719,6 +1762,12 @@ defmodule BlockScoutWeb.Etherscan do type: "string", description: "verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status." + }, + %{ + key: "not_decompiled_with_version", + type: "string", + description: + "Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts." } ], responses: [ @@ -1741,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 %{ name: "getabi", description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", @@ -1970,7 +2139,8 @@ defmodule BlockScoutWeb.Etherscan do actions: [ @contract_listcontracts_action, @contract_getabi_action, - @contract_getsourcecode_action + @contract_getsourcecode_action, + @contract_verify_action ] } diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex index ff0aec3423..e2969905cc 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex @@ -48,6 +48,11 @@ <%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %> +
+ <%= label f, :name, gettext("Optimization runs") %> + <%= text_input :optimization, :runs, value: 200, class: "form-control", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %> +
+
<%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code below") %> <%= textarea f, :contract_source_code, class: "form-control monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index fbe1813dce..27e049dfc3 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -22,6 +22,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do RPCView.render("error.json", assigns) 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 %{ "Address" => to_string(address_hash), @@ -36,19 +40,27 @@ defmodule BlockScoutWeb.API.RPC.ContractView do end defp prepare_source_code_contract(contract, _) do + decompiled_smart_contract = latest_decompiled_smart_contract(contract.decompiled_smart_contracts) + %{ "Address" => to_string(contract.address_hash), "SourceCode" => contract.contract_source_code, "ABI" => Jason.encode!(contract.abi), "ContractName" => contract.name, - "DecompiledSourceCode" => decompiled_source_code(contract.decompiled_smart_contract), - "DecompilerVersion" => decompiler_version(contract.decompiled_smart_contract), + "DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract), + "DecompilerVersion" => decompiler_version(decompiled_smart_contract), "CompilerVersion" => contract.compiler_version, "OptimizationUsed" => if(contract.optimization, do: "1", else: "0") } end - defp prepare_contract(%Address{hash: hash, smart_contract: nil, decompiled_smart_contract: decompiled_smart_contract}) do + defp prepare_contract(%Address{ + hash: hash, + smart_contract: nil, + decompiled_smart_contracts: decompiled_smart_contracts + }) do + decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts) + %{ "Address" => to_string(hash), "SourceCode" => "", @@ -64,8 +76,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do defp prepare_contract(%Address{ hash: hash, smart_contract: %SmartContract{} = contract, - decompiled_smart_contract: decompiled_smart_contract + decompiled_smart_contracts: decompiled_smart_contracts }) do + decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts) + %{ "Address" => to_string(hash), "SourceCode" => contract.contract_source_code, @@ -78,6 +92,12 @@ defmodule BlockScoutWeb.API.RPC.ContractView do } end + defp latest_decompiled_smart_contract([]), do: nil + + defp latest_decompiled_smart_contract(contracts) do + Enum.max_by(contracts, fn contract -> DateTime.to_unix(contract.inserted_at) end) + end + defp decompiled_source_code(nil), do: "Contract source code not decompiled." defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 79c8a939f8..28d3db09d8 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -197,7 +197,7 @@ msgid "Blocks Validated" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 msgid "Cancel" msgstr "" @@ -367,7 +367,7 @@ msgid "ETH" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57 msgid "Enter the Solidity Contract Code below" msgstr "" @@ -713,7 +713,7 @@ msgid "Request URL" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:128 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133 msgid "Reset" msgstr "" @@ -1024,7 +1024,7 @@ msgid "Verify & Publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132 msgid "Verify & publish" msgstr "" @@ -1165,7 +1165,7 @@ msgid "Loading..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 msgid "Loading...." msgstr "" @@ -1582,57 +1582,57 @@ msgid "Genesis Block" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 msgid "1 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 msgid "1 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 msgid "2 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 msgid "2 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 msgid "3 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 msgid "3 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 msgid "4 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 msgid "4 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116 msgid "5 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 msgid "5 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68 msgid "Contract Libraries" msgstr "" @@ -1698,16 +1698,10 @@ msgid "EVM Version" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 msgid "Enter constructor arguments if the contract had any" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:67 -#: lib/block_scout_web/templates/address/_tabs.html.eex:133 -msgid "Decompiled code" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20 msgid "Copy Decompiled Contract Code" @@ -1718,6 +1712,12 @@ msgstr "" msgid "Decompiled Code" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:67 +#: lib/block_scout_web/templates/address/_tabs.html.eex:133 +msgid "Decompiled code" +msgstr "" + #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18 msgid "Decompiled contract code" @@ -1727,3 +1727,8 @@ msgstr "" #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11 msgid "Decompiler version" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 +msgid "Optimization runs" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index aaa75f2b34..6dbdb2d676 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -197,7 +197,7 @@ msgid "Blocks Validated" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 msgid "Cancel" msgstr "" @@ -367,7 +367,7 @@ msgid "ETH" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57 msgid "Enter the Solidity Contract Code below" msgstr "" @@ -713,7 +713,7 @@ msgid "Request URL" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:128 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133 msgid "Reset" msgstr "" @@ -1024,7 +1024,7 @@ msgid "Verify & Publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132 msgid "Verify & publish" msgstr "" @@ -1165,7 +1165,7 @@ msgid "Loading..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 msgid "Loading...." msgstr "" @@ -1582,57 +1582,57 @@ msgid "Genesis Block" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 msgid "1 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 msgid "1 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 msgid "2 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 msgid "2 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 msgid "3 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 msgid "3 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 msgid "4 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 msgid "4 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116 msgid "5 Library Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 msgid "5 Library Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68 msgid "Contract Libraries" msgstr "" @@ -1698,16 +1698,10 @@ msgid "EVM Version" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 msgid "Enter constructor arguments if the contract had any" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:67 -#: lib/block_scout_web/templates/address/_tabs.html.eex:133 -msgid "Decompiled code" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20 msgid "Copy Decompiled Contract Code" @@ -1718,12 +1712,23 @@ msgstr "" msgid "Decompiled Code" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:67 +#: lib/block_scout_web/templates/address/_tabs.html.eex:133 +msgid "Decompiled code" +msgstr "" + #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18 msgid "Decompiled contract code" msgstr "" -#, elixir-format +#, elixir-format, fuzzy #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11 msgid "Decompiler version" msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 +msgid "Optimization runs" +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 766a691a62..632809863f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -1,5 +1,6 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do use BlockScoutWeb.ConnCase + alias Explorer.Factory describe "listcontracts" do setup do @@ -157,6 +158,60 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do ] end + test "filtering for only decompiled contracts, with a decompiled with version filter", %{params: params, conn: conn} do + insert(:decompiled_smart_contract, decompiler_version: "foobar") + smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz") + + response = + conn + |> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"})) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [ + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(smart_contract.address_hash), + "CompilerVersion" => "", + "ContractName" => "", + "DecompiledSourceCode" => smart_contract.decompiled_source_code, + "DecompilerVersion" => "bizbuz", + "OptimizationUsed" => "", + "SourceCode" => "" + } + ] + end + + test "filtering for only decompiled contracts, with a decompiled with version filter, where another decompiled version exists", + %{params: params, conn: conn} do + non_match = insert(:decompiled_smart_contract, decompiler_version: "foobar") + insert(:decompiled_smart_contract, decompiler_version: "bizbuz", address_hash: non_match.address_hash) + smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz") + + response = + conn + |> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"})) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [ + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(smart_contract.address_hash), + "CompilerVersion" => "", + "ContractName" => "", + "DecompiledSourceCode" => smart_contract.decompiled_source_code, + "DecompilerVersion" => "bizbuz", + "OptimizationUsed" => "", + "SourceCode" => "" + } + ] + end + test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do insert(:decompiled_smart_contract) insert(:smart_contract) @@ -359,4 +414,100 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" 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 diff --git a/apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json b/apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json new file mode 100644 index 0000000000..f74cd3d707 --- /dev/null +++ b/apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json @@ -0,0 +1,13 @@ +[ + { + "compiler_version": "v0.4.11+commit.68ef5810", + "contract": "pragma solidity ^0.4.11;\n/**\n * @title ERC20Basic\n * @dev Simpler version of ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/179\n */\ncontract ERC20Basic {\n uint256 public totalSupply;\n function balanceOf(address who) public constant returns (uint256);\n function transfer(address to, uint256 value) public returns (bool);\n event Transfer(address indexed from, address indexed to, uint256 value);\n}\n/**\n * @title Ownable\n * @dev The Ownable contract has an owner address, and provides basic authorization control\n * functions, this simplifies the implementation of \"user permissions\".\n */\ncontract Ownable {\n address public owner;\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n /**\n * @dev The Ownable constructor sets the original `owner` of the contract to the sender\n * account.\n */\n function Ownable() {\n owner = msg.sender;\n }\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(msg.sender == owner);\n _;\n }\n /**\n * @dev Allows the current owner to transfer control of the contract to a newOwner.\n * @param newOwner The address to transfer ownership to.\n */\n function transferOwnership(address newOwner) onlyOwner public {\n require(newOwner != address(0));\n OwnershipTransferred(owner, newOwner);\n owner = newOwner;\n }\n}\n// Temporarily have SafeMath here until all contracts have been migrated to SafeMathLib version from OpenZeppelin\n/**\n * Math operations with safety checks\n */\ncontract SafeMath {\n function safeMul(uint a, uint b) internal returns (uint) {\n uint c = a * b;\n assert(a == 0 || c / a == b);\n return c;\n }\n function safeDiv(uint a, uint b) internal returns (uint) {\n assert(b > 0);\n uint c = a / b;\n assert(a == b * c + a % b);\n return c;\n }\n function safeSub(uint a, uint b) internal returns (uint) {\n assert(b <= a);\n return a - b;\n }\n function safeAdd(uint a, uint b) internal returns (uint) {\n uint c = a + b;\n assert(c>=a && c>=b);\n return c;\n }\n function max64(uint64 a, uint64 b) internal constant returns (uint64) {\n return a >= b ? a : b;\n }\n function min64(uint64 a, uint64 b) internal constant returns (uint64) {\n return a < b ? a : b;\n }\n function max256(uint256 a, uint256 b) internal constant returns (uint256) {\n return a >= b ? a : b;\n }\n function min256(uint256 a, uint256 b) internal constant returns (uint256) {\n return a < b ? a : b;\n }\n}\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ncontract ERC20 is ERC20Basic {\n function allowance(address owner, address spender) public constant returns (uint256);\n function transferFrom(address from, address to, uint256 value) public returns (bool);\n function approve(address spender, uint256 value) public returns (bool);\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n/**\n * Standard ERC20 token with Short Hand Attack and approve() race condition mitigation.\n *\n * Based on code by FirstBlood:\n * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol\n */\ncontract StandardToken is ERC20, SafeMath {\n /* Token supply got increased and a new owner received these tokens */\n event Minted(address receiver, uint amount);\n /* Actual balances of token holders */\n mapping(address => uint) balances;\n /* approve() allowances */\n mapping (address => mapping (address => uint)) allowed;\n /* Interface declaration */\n function isToken() public constant returns (bool weAre) {\n return true;\n }\n function transfer(address _to, uint _value) returns (bool success) {\n balances[msg.sender] = safeSub(balances[msg.sender], _value);\n balances[_to] = safeAdd(balances[_to], _value);\n Transfer(msg.sender, _to, _value);\n return true;\n }\n function transferFrom(address _from, address _to, uint _value) returns (bool success) {\n uint _allowance = allowed[_from][msg.sender];\n balances[_to] = safeAdd(balances[_to], _value);\n balances[_from] = safeSub(balances[_from], _value);\n allowed[_from][msg.sender] = safeSub(_allowance, _value);\n Transfer(_from, _to, _value);\n return true;\n }\n function balanceOf(address _owner) constant returns (uint balance) {\n return balances[_owner];\n }\n function approve(address _spender, uint _value) returns (bool success) {\n // To change the approve amount you first have to reduce the addresses`\n // allowance to zero by calling `approve(_spender, 0)` if it is not\n // already 0 to mitigate the race condition described here:\n // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;\n allowed[msg.sender][_spender] = _value;\n Approval(msg.sender, _spender, _value);\n return true;\n }\n function allowance(address _owner, address _spender) constant returns (uint remaining) {\n return allowed[_owner][_spender];\n }\n}\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * Upgrade agent interface inspired by Lunyr.\n *\n * Upgrade agent transfers tokens to a new contract.\n * Upgrade agent itself can be the token contract, or just a middle man contract doing the heavy lifting.\n */\ncontract UpgradeAgent {\n uint public originalSupply;\n /** Interface marker */\n function isUpgradeAgent() public constant returns (bool) {\n return true;\n }\n function upgradeFrom(address _from, uint256 _value) public;\n}\n/**\n * A token upgrade mechanism where users can opt-in amount of tokens to the next smart contract revision.\n *\n * First envisioned by Golem and Lunyr projects.\n */\ncontract UpgradeableToken is StandardToken {\n /** Contract / person who can set the upgrade path. This can be the same as team multisig wallet, as what it is with its default value. */\n address public upgradeMaster;\n /** The next contract where the tokens will be migrated. */\n UpgradeAgent public upgradeAgent;\n /** How many tokens we have upgraded by now. */\n uint256 public totalUpgraded;\n /**\n * Upgrade states.\n *\n * - NotAllowed: The child contract has not reached a condition where the upgrade can bgun\n * - WaitingForAgent: Token allows upgrade, but we don't have a new agent yet\n * - ReadyToUpgrade: The agent is set, but not a single token has been upgraded yet\n * - Upgrading: Upgrade agent is set and the balance holders can upgrade their tokens\n *\n */\n enum UpgradeState {Unknown, NotAllowed, WaitingForAgent, ReadyToUpgrade, Upgrading}\n /**\n * Somebody has upgraded some of his tokens.\n */\n event Upgrade(address indexed _from, address indexed _to, uint256 _value);\n /**\n * New upgrade agent available.\n */\n event UpgradeAgentSet(address agent);\n /**\n * Do not allow construction without upgrade master set.\n */\n function UpgradeableToken(address _upgradeMaster) {\n upgradeMaster = _upgradeMaster;\n }\n /**\n * Allow the token holder to upgrade some of their tokens to a new contract.\n */\n function upgrade(uint256 value) public {\n UpgradeState state = getUpgradeState();\n if(!(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading)) {\n // Called in a bad state\n throw;\n }\n // Validate input value.\n if (value == 0) throw;\n balances[msg.sender] = safeSub(balances[msg.sender], value);\n // Take tokens out from circulation\n totalSupply = safeSub(totalSupply, value);\n totalUpgraded = safeAdd(totalUpgraded, value);\n // Upgrade agent reissues the tokens\n upgradeAgent.upgradeFrom(msg.sender, value);\n Upgrade(msg.sender, upgradeAgent, value);\n }\n /**\n * Set an upgrade agent that handles\n */\n function setUpgradeAgent(address agent) external {\n if(!canUpgrade()) {\n // The token is not yet in a state that we could think upgrading\n throw;\n }\n if (agent == 0x0) throw;\n // Only a master can designate the next agent\n if (msg.sender != upgradeMaster) throw;\n // Upgrade has already begun for an agent\n if (getUpgradeState() == UpgradeState.Upgrading) throw;\n upgradeAgent = UpgradeAgent(agent);\n // Bad interface\n if(!upgradeAgent.isUpgradeAgent()) throw;\n // Make sure that token supplies match in source and target\n if (upgradeAgent.originalSupply() != totalSupply) throw;\n UpgradeAgentSet(upgradeAgent);\n }\n /**\n * Get the state of the token upgrade.\n */\n function getUpgradeState() public constant returns(UpgradeState) {\n if(!canUpgrade()) return UpgradeState.NotAllowed;\n else if(address(upgradeAgent) == 0x00) return UpgradeState.WaitingForAgent;\n else if(totalUpgraded == 0) return UpgradeState.ReadyToUpgrade;\n else return UpgradeState.Upgrading;\n }\n /**\n * Change the upgrade master.\n *\n * This allows us to set a new owner for the upgrade mechanism.\n */\n function setUpgradeMaster(address master) public {\n if (master == 0x0) throw;\n if (msg.sender != upgradeMaster) throw;\n upgradeMaster = master;\n }\n /**\n * Child contract can enable to provide the condition when the upgrade can begun.\n */\n function canUpgrade() public constant returns(bool) {\n return true;\n }\n}\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * Define interface for releasing the token transfer after a successful crowdsale.\n */\ncontract ReleasableToken is ERC20, Ownable {\n /* The finalizer contract that allows unlift the transfer limits on this token */\n address public releaseAgent;\n /** A crowdsale contract can release us to the wild if ICO success. If false we are are in transfer lock up period.*/\n bool public released = false;\n /** Map of agents that are allowed to transfer tokens regardless of the lock down period. These are crowdsale contracts and possible the team multisig itself. */\n mapping (address => bool) public transferAgents;\n /**\n * Limit token transfer until the crowdsale is over.\n *\n */\n modifier canTransfer(address _sender) {\n if(!released) {\n if(!transferAgents[_sender]) {\n throw;\n }\n }\n _;\n }\n /**\n * Set the contract that can call release and make the token transferable.\n *\n * Design choice. Allow reset the release agent to fix fat finger mistakes.\n */\n function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {\n // We don't do interface check here as we might want to a normal wallet address to act as a release agent\n releaseAgent = addr;\n }\n /**\n * Owner can allow a particular address (a crowdsale contract) to transfer tokens despite the lock up period.\n */\n function setTransferAgent(address addr, bool state) onlyOwner inReleaseState(false) public {\n transferAgents[addr] = state;\n }\n /**\n * One way function to release the tokens to the wild.\n *\n * Can be called only from the release agent that is the final ICO contract. It is only called if the crowdsale has been success (first milestone reached).\n */\n function releaseTokenTransfer() public onlyReleaseAgent {\n released = true;\n }\n /** The function can be called only before or after the tokens have been releasesd */\n modifier inReleaseState(bool releaseState) {\n if(releaseState != released) {\n throw;\n }\n _;\n }\n /** The function can be called only by a whitelisted release agent. */\n modifier onlyReleaseAgent() {\n if(msg.sender != releaseAgent) {\n throw;\n }\n _;\n }\n function transfer(address _to, uint _value) canTransfer(msg.sender) returns (bool success) {\n // Call StandardToken.transfer()\n return super.transfer(_to, _value);\n }\n function transferFrom(address _from, address _to, uint _value) canTransfer(_from) returns (bool success) {\n // Call StandardToken.transferForm()\n return super.transferFrom(_from, _to, _value);\n }\n}\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * This smart contract code is Copyright 2017 TokenMarket Ltd. For more information see https://tokenmarket.net\n *\n * Licensed under the Apache License, version 2.0: https://github.com/TokenMarketNet/ico/blob/master/LICENSE.txt\n */\n/**\n * Safe unsigned safe math.\n *\n * https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736#.750gwtwli\n *\n * Originally from https://raw.githubusercontent.com/AragonOne/zeppelin-solidity/master/contracts/SafeMathLib.sol\n *\n * Maintained here until merged to mainline zeppelin-solidity.\n *\n */\nlibrary SafeMathLibExt {\n function times(uint a, uint b) returns (uint) {\n uint c = a * b;\n assert(a == 0 || c / a == b);\n return c;\n }\n function divides(uint a, uint b) returns (uint) {\n assert(b > 0);\n uint c = a / b;\n assert(a == b * c + a % b);\n return c;\n }\n function minus(uint a, uint b) returns (uint) {\n assert(b <= a);\n return a - b;\n }\n function plus(uint a, uint b) returns (uint) {\n uint c = a + b;\n assert(c>=a);\n return c;\n }\n}\n/**\n * A token that can increase its supply by another contract.\n *\n * This allows uncapped crowdsale by dynamically increasing the supply when money pours in.\n * Only mint agents, contracts whitelisted by owner, can mint new tokens.\n *\n */\ncontract MintableTokenExt is StandardToken, Ownable {\n using SafeMathLibExt for uint;\n bool public mintingFinished = false;\n /** List of agents that are allowed to create new tokens */\n mapping (address => bool) public mintAgents;\n event MintingAgentChanged(address addr, bool state );\n struct ReservedTokensData {\n uint inTokens;\n uint inPercentage;\n }\n mapping (address => ReservedTokensData) public reservedTokensList;\n address[] public reservedTokensDestinations;\n uint public reservedTokensDestinationsLen = 0;\n function setReservedTokensList(address addr, uint inTokens, uint inPercentage) onlyOwner {\n reservedTokensDestinations.push(addr);\n reservedTokensDestinationsLen++;\n reservedTokensList[addr] = ReservedTokensData({inTokens:inTokens, inPercentage:inPercentage});\n }\n function getReservedTokensListValInTokens(address addr) constant returns (uint inTokens) {\n return reservedTokensList[addr].inTokens;\n }\n function getReservedTokensListValInPercentage(address addr) constant returns (uint inPercentage) {\n return reservedTokensList[addr].inPercentage;\n }\n function setReservedTokensListMultiple(address[] addrs, uint[] inTokens, uint[] inPercentage) onlyOwner {\n for (uint iterator = 0; iterator < addrs.length; iterator++) {\n setReservedTokensList(addrs[iterator], inTokens[iterator], inPercentage[iterator]);\n }\n }\n /**\n * Create new tokens and allocate them to an address..\n *\n * Only callably by a crowdsale contract (mint agent).\n */\n function mint(address receiver, uint amount) onlyMintAgent canMint public {\n totalSupply = totalSupply.plus(amount);\n balances[receiver] = balances[receiver].plus(amount);\n // This will make the mint transaction apper in EtherScan.io\n // We can remove this after there is a standardized minting event\n Transfer(0, receiver, amount);\n }\n /**\n * Owner can allow a crowdsale contract to mint new tokens.\n */\n function setMintAgent(address addr, bool state) onlyOwner canMint public {\n mintAgents[addr] = state;\n MintingAgentChanged(addr, state);\n }\n modifier onlyMintAgent() {\n // Only crowdsale contracts are allowed to mint new tokens\n if(!mintAgents[msg.sender]) {\n throw;\n }\n _;\n }\n /** Make sure we are not done yet. */\n modifier canMint() {\n if(mintingFinished) throw;\n _;\n }\n}\n/**\n * A crowdsaled token.\n *\n * An ERC-20 token designed specifically for crowdsales with investor protection and further development path.\n *\n * - The token transfer() is disabled until the crowdsale is over\n * - The token contract gives an opt-in upgrade path to a new contract\n * - The same token can be part of several crowdsales through approve() mechanism\n * - The token can be capped (supply set in the constructor) or uncapped (crowdsale contract can mint new tokens)\n *\n */\ncontract CrowdsaleTokenExt is ReleasableToken, MintableTokenExt, UpgradeableToken {\n /** Name and symbol were updated. */\n event UpdatedTokenInformation(string newName, string newSymbol);\n string public name;\n string public symbol;\n uint public decimals;\n /* Minimum ammount of tokens every buyer can buy. */\n uint public minCap;\n /**\n * Construct the token.\n *\n * This token must be created through a team multisig wallet, so that it is owned by that wallet.\n *\n * @param _name Token name\n * @param _symbol Token symbol - should be all caps\n * @param _initialSupply How many tokens we start with\n * @param _decimals Number of decimal places\n * @param _mintable Are new tokens created over the crowdsale or do we distribute only the initial supply? Note that when the token becomes transferable the minting always ends.\n */\n function CrowdsaleTokenExt(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable, uint _globalMinCap)\n UpgradeableToken(msg.sender) {\n // Create any address, can be transferred\n // to team multisig via changeOwner(),\n // also remember to call setUpgradeMaster()\n owner = msg.sender;\n name = _name;\n symbol = _symbol;\n totalSupply = _initialSupply;\n decimals = _decimals;\n minCap = _globalMinCap;\n // Create initially all balance on the team multisig\n balances[owner] = totalSupply;\n if(totalSupply > 0) {\n Minted(owner, totalSupply);\n }\n // No more new supply allowed after the token creation\n if(!_mintable) {\n mintingFinished = true;\n if(totalSupply == 0) {\n throw; // Cannot create a token without supply and no minting\n }\n }\n }\n /**\n * When token is released to be transferable, enforce no new tokens can be created.\n */\n function releaseTokenTransfer() public onlyReleaseAgent {\n mintingFinished = true;\n super.releaseTokenTransfer();\n }\n /**\n * Allow upgrade agent functionality kick in only if the crowdsale was success.\n */\n function canUpgrade() public constant returns(bool) {\n return released && super.canUpgrade();\n }\n /**\n * Owner can update token information here.\n *\n * It is often useful to conceal the actual token association, until\n * the token operations, like central issuance or reissuance have been completed.\n *\n * This function allows the token owner to rename the token after the operations\n * have been completed and then point the audience to use the token contract.\n */\n function setTokenInformation(string _name, string _symbol) onlyOwner {\n name = _name;\n symbol = _symbol;\n UpdatedTokenInformation(name, symbol);\n }\n}", + "expected_bytecode": "606060405236156101e05763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166302f652a381146101e257806305d2035b1461020557806306fdde0314610229578063095ea7b3146102b957806318160ddd146102ec57806323b872dd1461030e57806329ff4f5314610347578063313ce56714610365578063338f43a0146103875780633fa615b0146103b557806340c10f19146103d757806342c1867b146103f8578063432146751461042857806345977d031461044b5780634eee966f1461046057806351ed17a4146104f55780635de4ccb01461052a5780635f412d4f14610556578063600440cb1461056857806370a08231146105945780637386f0a7146105c257806380c190cf146105f15780638444b3911461061f578063867c2857146106535780638da5cb5b1461068357806395d89b41146106af578063961325211461073f5780639738968c14610763578063a9059cbb14610787578063b4ffaece146107ba578063c33105171461087f578063c752ff62146108a1578063d1f276d3146108c3578063d7e7088a146108ef578063dd62ed3e1461090d578063eefa597b14610941578063f2fde38b14610965578063f30f850814610983578063ffeb7d75146109a7575bfe5b34156101ea57fe5b610203600160a060020a036004351660243515156109c5565b005b341561020d57fe5b610215610a28565b604080519115158252519081900360200190f35b341561023157fe5b610239610a31565b60408051602080825283518183015283519192839290830191850190808383821561027f575b80518252602083111561027f57601f19909201916020918201910161025f565b505050905090810190601f1680156102ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102c157fe5b610215600160a060020a0360043516602435610abf565b604080519115158252519081900360200190f35b34156102f457fe5b6102fc610b66565b60408051918252519081900360200190f35b341561031657fe5b610215600160a060020a0360043581169060243516604435610b6c565b604080519115158252519081900360200190f35b341561034f57fe5b610203600160a060020a0360043516610bc4565b005b341561036d57fe5b6102fc610c2a565b60408051918252519081900360200190f35b341561038f57fe5b6102fc600160a060020a0360043516610c30565b60408051918252519081900360200190f35b34156103bd57fe5b6102fc610c4f565b60408051918252519081900360200190f35b34156103df57fe5b610203600160a060020a0360043516602435610c55565b005b341561040057fe5b610215600160a060020a0360043516610e1b565b604080519115158252519081900360200190f35b341561043057fe5b610203600160a060020a03600435166024351515610e30565b005b341561045357fe5b610203600435610ec4565b005b341561046857fe5b610203600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375050604080516020601f89358b0180359182018390048302840183019094528083529799988101979196509182019450925082915084018382808284375094965061103495505050505050565b005b34156104fd57fe5b610511600160a060020a03600435166111a8565b6040805192835260208301919091528051918290030190f35b341561053257fe5b61053a6111c1565b60408051600160a060020a039092168252519081900360200190f35b341561055e57fe5b6102036111d0565b005b341561057057fe5b61053a611205565b60408051600160a060020a039092168252519081900360200190f35b341561059c57fe5b6102fc600160a060020a0360043516611214565b60408051918252519081900360200190f35b34156105ca57fe5b61053a600435611233565b60408051600160a060020a039092168252519081900360200190f35b34156105f957fe5b6102fc600160a060020a0360043516611265565b60408051918252519081900360200190f35b341561062757fe5b61062f611287565b6040518082600481111561063f57fe5b60ff16815260200191505060405180910390f35b341561065b57fe5b610215600160a060020a03600435166112d4565b604080519115158252519081900360200190f35b341561068b57fe5b61053a6112e9565b60408051600160a060020a039092168252519081900360200190f35b34156106b757fe5b6102396112f8565b60408051602080825283518183015283519192839290830191850190808383821561027f575b80518252602083111561027f57601f19909201916020918201910161025f565b505050905090810190601f1680156102ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561074757fe5b610215611386565b604080519115158252519081900360200190f35b341561076b57fe5b610215611396565b604080519115158252519081900360200190f35b341561078f57fe5b610215600160a060020a03600435166024356113bc565b604080519115158252519081900360200190f35b34156107c257fe5b610203600480803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843750506040805187358901803560208181028481018201909552818452989a998901989297509082019550935083925085019084908082843750506040805187358901803560208181028481018201909552818452989a99890198929750908201955093508392508501908490808284375094965061141295505050505050565b005b341561088757fe5b6102fc61149f565b60408051918252519081900360200190f35b34156108a957fe5b6102fc6114a5565b60408051918252519081900360200190f35b34156108cb57fe5b61053a6114ab565b60408051600160a060020a039092168252519081900360200190f35b34156108f757fe5b610203600160a060020a03600435166114ba565b005b341561091557fe5b6102fc600160a060020a0360043581169060243516611697565b60408051918252519081900360200190f35b341561094957fe5b6102156116c4565b604080519115158252519081900360200190f35b341561096d57fe5b610203600160a060020a03600435166116ca565b005b341561098b57fe5b610203600160a060020a0360043516602435604435611763565b005b34156109af57fe5b610203600160a060020a03600435166117fe565b005b60035433600160a060020a039081169116146109e15760006000fd5b60045460009060a060020a900460ff16156109fc5760006000fd5b600160a060020a0383166000908152600560205260409020805460ff19168315151790555b5b505b5050565b60065460ff1681565b600e805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610ab75780601f10610a8c57610100808354040283529160200191610ab7565b820191906000526020600020905b815481529060010190602001808311610a9a57829003601f168201915b505050505081565b60008115801590610af45750600160a060020a0333811660009081526002602090815260408083209387168352929052205415155b15610aff5760006000fd5b600160a060020a03338116600081815260026020908152604080832094881680845294825291829020869055815186815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a35060015b92915050565b60005481565b600454600090849060a060020a900460ff161515610bac57600160a060020a03811660009081526005602052604090205460ff161515610bac5760006000fd5b5b610bb885858561185c565b91505b5b509392505050565b60035433600160a060020a03908116911614610be05760006000fd5b60045460009060a060020a900460ff1615610bfb5760006000fd5b6004805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5b505b50565b60105481565b600160a060020a0381166000908152600860205260409020545b919050565b60115481565b600160a060020a03331660009081526007602052604090205460ff161515610c7d5760006000fd5b60065460ff1615610c8e5760006000fd5b6000547354ca5a7c536dbed5897b78d30a93dcd0e46fbdac6366098d4f9091836000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808381526020018281526020019250505060206040518083038186803b1515610d0a57fe5b6102c65a03f41515610d1857fe5b50506040805180516000908155600160a060020a038616815260016020908152838220549281019190915282517f66098d4f00000000000000000000000000000000000000000000000000000000815260048101929092526024820185905291517354ca5a7c536dbed5897b78d30a93dcd0e46fbdac93506366098d4f92604480840193919291829003018186803b1515610daf57fe5b6102c65a03f41515610dbd57fe5b5050604080518051600160a060020a0386166000818152600160209081528582209390935586845293519094507fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35b5b5b5050565b60076020526000908152604090205460ff1681565b60035433600160a060020a03908116911614610e4c5760006000fd5b60065460ff1615610e5d5760006000fd5b600160a060020a038216600081815260076020908152604091829020805460ff191685151590811790915582519384529083015280517f4b0adf6c802794c7dde28a08a4e07131abcff3bf9603cd71f14f90bec7865efa9281900390910190a15b5b5b5050565b6000610ece611287565b905060035b816004811115610edf57fe5b1480610ef7575060045b816004811115610ef557fe5b145b1515610f035760006000fd5b811515610f105760006000fd5b600160a060020a033316600090815260016020526040902054610f33908361195f565b600160a060020a03331660009081526001602052604081209190915554610f5a908361195f565b600055600d54610f6a9083611976565b600d55600c54604080517f753e88e5000000000000000000000000000000000000000000000000000000008152600160a060020a033381166004830152602482018690529151919092169163753e88e591604480830192600092919082900301818387803b1515610fd757fe5b6102c65a03f11515610fe557fe5b5050600c54604080518581529051600160a060020a03928316935033909216917f7e5c344a8141a805725cb476f76c6953b842222b967edd1f78ddb6e8b3f397ac9181900360200190a35b5050565b60035433600160a060020a039081169116146110505760006000fd5b815161106390600e906020850190611a9c565b50805161107790600f906020840190611a9c565b5060408051818152600e8054600260001961010060018416150201909116049282018390527fd131ab1e6f279deea74e13a18477e13e2107deb6dc8ae955648948be5841fb46929091600f918190602082019060608301908690801561111e5780601f106110f35761010080835404028352916020019161111e565b820191906000526020600020905b81548152906001019060200180831161110157829003601f168201915b50508381038252845460026000196101006001841615020190911604808252602090910190859080156111925780601f1061116757610100808354040283529160200191611192565b820191906000526020600020905b81548152906001019060200180831161117557829003601f168201915b505094505050505060405180910390a15b5b5050565b6008602052600090815260409020805460019091015482565b600c54600160a060020a031681565b60045433600160a060020a039081169116146111ec5760006000fd5b6006805460ff1916600117905561120161199e565b5b5b565b600b54600160a060020a031681565b600160a060020a0381166000908152600160205260409020545b919050565b600980548290811061124157fe5b906000526020600020900160005b915054906101000a9004600160a060020a031681565b600160a060020a0381166000908152600860205260409020600101545b919050565b6000611291611396565b151561129f575060016112ce565b600c54600160a060020a031615156112b9575060026112ce565b600d5415156112ca575060036112ce565b5060045b5b5b5b90565b60056020526000908152604090205460ff1681565b600354600160a060020a031681565b600f805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610ab75780601f10610a8c57610100808354040283529160200191610ab7565b820191906000526020600020905b815481529060010190602001808311610a9a57829003601f168201915b505050505081565b60045460a060020a900460ff1681565b60045460009060a060020a900460ff1680156113b557506113b56116c4565b5b90505b90565b600454600090339060a060020a900460ff1615156113fc57600160a060020a03811660009081526005602052604090205460ff1615156113fc5760006000fd5b5b61140784846119e8565b91505b5b5092915050565b60035460009033600160a060020a039081169116146114315760006000fd5b5060005b83518110156114975761148e848281518110151561144f57fe5b90602001906020020151848381518110151561146757fe5b90602001906020020151848481518110151561147f57fe5b90602001906020020151611763565b5b600101611435565b5b5b50505050565b600a5481565b600d5481565b600454600160a060020a031681565b6114c2611396565b15156114ce5760006000fd5b600160a060020a03811615156114e45760006000fd5b600b5433600160a060020a039081169116146115005760006000fd5b60045b61150b611287565b600481111561151657fe5b14156115225760006000fd5b600c805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038381169190911791829055604080516000602091820181905282517f61d3d7a6000000000000000000000000000000000000000000000000000000008152925194909316936361d3d7a6936004808501948390030190829087803b15156115a857fe5b6102c65a03f115156115b657fe5b505060405151151590506115ca5760006000fd5b60008054600c5460408051602090810185905281517f4b2ba0dd00000000000000000000000000000000000000000000000000000000815291519394600160a060020a0390931693634b2ba0dd936004808501948390030190829087803b151561163057fe5b6102c65a03f1151561163e57fe5b5050604051519190911490506116545760006000fd5b600c5460408051600160a060020a039092168252517f7845d5aa74cc410e35571258d954f23b82276e160fe8c188fa80566580f279cc9181900360200190a15b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b60015b90565b60035433600160a060020a039081169116146116e65760006000fd5b600160a060020a03811615156116fc5760006000fd5b600354604051600160a060020a038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36003805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b50565b60035433600160a060020a0390811691161461177f5760006000fd5b60098054600181016117918382611b1b565b916000526020600020900160005b8154600160a060020a038088166101009390930a83810291021990911617909155600a80546001908101909155604080518082018252868152602080820187815260009586526008909152919093209251835551910155505b5b505050565b600160a060020a03811615156118145760006000fd5b600b5433600160a060020a039081169116146118305760006000fd5b600b805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b50565b600160a060020a03808416600090815260026020908152604080832033851684528252808320549386168352600190915281205490919061189d9084611976565b600160a060020a0380861660009081526001602052604080822093909355908716815220546118cc908461195f565b600160a060020a0386166000908152600160205260409020556118ef818461195f565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60008282111561196b57fe5b508082035b92915050565b600082820183811080159061198b5750828110155b151561199357fe5b8091505b5092915050565b60045433600160a060020a039081169116146119ba5760006000fd5b6004805474ff0000000000000000000000000000000000000000191660a060020a1790555b5b565b60015b90565b600160a060020a033316600090815260016020526040812054611a0b908361195f565b600160a060020a033381166000908152600160205260408082209390935590851681522054611a3a9083611976565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b92915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611add57805160ff1916838001178555611b0a565b82800160010185558215611b0a579182015b82811115611b0a578251825591602001919060010190611aef565b5b50611b17929150611b45565b5090565b815481835581811511610a2157600083815260209020610a21918101908301611b45565b5b505050565b6112ce91905b80821115611b175760008155600101611b4b565b5090565b905600a165627a7a72305820c38ce552da1339b41e837cc46418755767ab24eb6b08726516f1e03f7b96fca9002900000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085570666972696e6700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035546520000000000000000000000000000000000000000000000000000000000", + "external_libraries": { + "SafeMathLibExt": "0x54ca5a7c536dbed5897b78d30a93dcd0e46fbdac" + }, + "name": "CrowdsaleTokenExt", + "optimize": true + } + ] + \ No newline at end of file diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index 57010769c0..c372c0c979 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -46,6 +46,7 @@ defmodule EthereumJSONRPC.Contract do |> case do {:ok, responses} -> responses {:error, {:bad_gateway, _request_url}} -> raise "Bad gateway" + {:error, reason} when is_atom(reason) -> raise Atom.to_string(reason) {:error, error} -> raise error end |> Enum.into(%{}, &{&1.id, &1}) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 0c65227931..d882e56b51 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -20,6 +20,7 @@ defmodule Explorer.Chain do import EthereumJSONRPC, only: [integer_to_quantity: 1] + alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi} alias Explorer.Chain.{ @@ -1898,7 +1899,16 @@ defmodule Explorer.Chain do """ @spec transaction_estimated_count() :: non_neg_integer() def transaction_estimated_count do - TransactionCountCache.value() + cached_value = TransactionCountCache.value() + + if is_nil(cached_value) do + %Postgrex.Result{rows: [[rows]]} = + SQL.query!(Repo, "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='transactions'") + + rows + else + cached_value + end end @doc """ @@ -2638,37 +2648,38 @@ defmodule Explorer.Chain do @spec data() :: Dataloader.Ecto.t() def data, do: DataloaderEcto.new(Repo) - @doc """ - Returns a list of block numbers with invalid consensus. - """ - @spec list_block_numbers_with_invalid_consensus :: [integer()] - def list_block_numbers_with_invalid_consensus do - query = - from( - block in Block, - join: parent in Block, - on: parent.hash == block.parent_hash, - where: block.consensus == true, - where: parent.consensus == false, - select: parent.number - ) - - Repo.all(query, timeout: :infinity) - end - - def list_decompiled_contracts(limit, offset) do + def list_decompiled_contracts(limit, offset, not_decompiled_with_version \\ nil) do query = from( address in Address, - join: decompiled_smart_contract in DecompiledSmartContract, - on: decompiled_smart_contract.address_hash == address.hash, - preload: [{:decompiled_smart_contract, decompiled_smart_contract}, :smart_contract], + where: + fragment( + "EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)", + address.hash + ), + preload: [:decompiled_smart_contracts, :smart_contract], order_by: [asc: address.inserted_at], limit: ^limit, offset: ^offset ) - Repo.all(query) + query + |> filter_decompiled_with_version(not_decompiled_with_version) + |> Repo.all() + end + + defp filter_decompiled_with_version(query, nil) do + query + end + + defp filter_decompiled_with_version(query, not_decompiled_with_version) do + from(address in query, + left_join: decompiled_smart_contract in DecompiledSmartContract, + on: decompiled_smart_contract.decompiler_version == ^not_decompiled_with_version, + on: decompiled_smart_contract.address_hash == address.hash, + where: is_nil(decompiled_smart_contract.id), + distinct: [address.hash] + ) end def list_verified_contracts(limit, offset) do @@ -2678,7 +2689,7 @@ defmodule Explorer.Chain do where: not is_nil(address.contract_code), join: smart_contract in SmartContract, on: smart_contract.address_hash == address.hash, - preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract], + preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts], order_by: [asc: address.inserted_at], limit: ^limit, offset: ^offset @@ -2692,7 +2703,7 @@ defmodule Explorer.Chain do from( address in Address, where: not is_nil(address.contract_code), - preload: [:smart_contract, :decompiled_smart_contract], + preload: [:smart_contract, :decompiled_smart_contracts], order_by: [asc: address.inserted_at], limit: ^limit, offset: ^offset @@ -2709,7 +2720,7 @@ defmodule Explorer.Chain do on: smart_contract.address_hash == address.hash, where: not is_nil(address.contract_code), where: is_nil(smart_contract.address_hash), - preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract], + preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts], order_by: [asc: address.inserted_at], limit: ^limit, offset: ^offset @@ -2722,11 +2733,16 @@ defmodule Explorer.Chain do query = from( address in Address, + where: + fragment( + "NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)", + address.hash + ), left_join: smart_contract in SmartContract, on: smart_contract.address_hash == address.hash, left_join: decompiled_smart_contract in DecompiledSmartContract, on: decompiled_smart_contract.address_hash == address.hash, - preload: [smart_contract: smart_contract, decompiled_smart_contract: decompiled_smart_contract], + preload: [:smart_contract, :decompiled_smart_contracts], where: not is_nil(address.contract_code), where: is_nil(smart_contract.address_hash), where: is_nil(decompiled_smart_contract.address_hash), diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index d4c341c7db..d4b5bc8174 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -62,6 +62,7 @@ defmodule Explorer.Chain.Address do except: [ :__meta__, :smart_contract, + :decompiled_smart_contract, :token, :contracts_creation_internal_transaction, :contracts_creation_transaction, @@ -77,7 +78,6 @@ defmodule Explorer.Chain.Address do field(:has_decompiled_code?, :boolean, virtual: true) has_one(:smart_contract, SmartContract) - has_one(:decompiled_smart_contract, DecompiledSmartContract) has_one(:token, Token, foreign_key: :contract_address_hash) has_one( diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 100f120290..209d5d2fa6 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -46,7 +46,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do |> Map.put(:timestamps, timestamps) ordered_consensus_block_numbers = ordered_consensus_block_numbers(changes_list) - where_invalid_parent = where_invalid_parent(changes_list) + where_invalid_neighbour = where_invalid_neighbour(changes_list) where_forked = where_forked(changes_list) multi @@ -70,8 +70,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do |> Multi.run(:lose_consensus, fn repo, _ -> lose_consensus(repo, ordered_consensus_block_numbers, insert_options) end) - |> Multi.run(:lose_invalid_parent_consensus, fn repo, _ -> - lose_invalid_parent_consensus(repo, where_invalid_parent, insert_options) + |> Multi.run(:lose_invalid_neighbour_consensus, fn repo, _ -> + lose_invalid_neighbour_consensus(repo, where_invalid_neighbour, insert_options) end) |> Multi.run(:delete_address_token_balances, fn repo, _ -> delete_address_token_balances(repo, ordered_consensus_block_numbers, insert_options) @@ -316,13 +316,13 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end end - defp lose_invalid_parent_consensus(repo, where_invalid_parent, %{ + defp lose_invalid_neighbour_consensus(repo, where_invalid_neighbour, %{ timeout: timeout, timestamps: %{updated_at: updated_at} }) do query = from( - block in where_invalid_parent, + block in where_invalid_neighbour, update: [ set: [ consensus: false, @@ -338,7 +338,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do {:ok, result} rescue postgrex_error in Postgrex.Error -> - {:error, %{exception: postgrex_error, where_invalid_parent: where_invalid_parent}} + {:error, %{exception: postgrex_error, where_invalid_neighbour: where_invalid_neighbour}} end end @@ -581,12 +581,22 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end) end - defp where_invalid_parent(blocks_changes) when is_list(blocks_changes) do + defp where_invalid_neighbour(blocks_changes) when is_list(blocks_changes) do initial = from(b in Block, where: false) - Enum.reduce(blocks_changes, initial, fn %{consensus: consensus, parent_hash: parent_hash, number: number}, acc -> + Enum.reduce(blocks_changes, initial, fn %{ + consensus: consensus, + hash: hash, + parent_hash: parent_hash, + number: number + }, + acc -> if consensus do - from(block in acc, or_where: block.number == ^(number - 1) and block.hash != ^parent_hash) + from( + block in acc, + or_where: block.number == ^(number - 1) and block.hash != ^parent_hash, + or_where: block.number == ^(number + 1) and block.parent_hash != ^hash + ) else acc end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index a117a77353..937c1d422f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -209,8 +209,8 @@ defmodule Explorer.Chain.SmartContract do field(:constructor_arguments, :string) field(:abi, {:array, :map}) - has_one( - :decompiled_smart_contract, + has_many( + :decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash ) @@ -227,7 +227,7 @@ defmodule Explorer.Chain.SmartContract do end def preload_decompiled_smart_contract(contract) do - Repo.preload(contract, :decompiled_smart_contract) + Repo.preload(contract, :decompiled_smart_contracts) end def changeset(%__MODULE__{} = smart_contract, attrs) do diff --git a/apps/explorer/lib/explorer/chain/transaction_count_cache.ex b/apps/explorer/lib/explorer/chain/transaction_count_cache.ex index 60ecc05285..5e441734c2 100644 --- a/apps/explorer/lib/explorer/chain/transaction_count_cache.ex +++ b/apps/explorer/lib/explorer/chain/transaction_count_cache.ex @@ -12,7 +12,7 @@ defmodule Explorer.Chain.TransactionCountCache do # 2 hours @cache_period 1_000 * 60 * 60 * 2 - @default_value 0 + @default_value nil @key "count" @name __MODULE__ diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index 2eab371445..e7f6090707 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -13,10 +13,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do ## Examples - iex(1)> Explorer.SmartContract.Solidity.CodeCompiler.run( - ...> "SimpleStorage", - ...> "v0.4.24+commit.e67f0147", - ...> \""" + iex(1)> Explorer.SmartContract.Solidity.CodeCompiler.run([ + ...> name: "SimpleStorage", + ...> compiler_version: "v0.4.24+commit.e67f0147", + ...> code: \""" ...> pragma solidity ^0.4.24; ...> ...> contract SimpleStorage { @@ -31,8 +31,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do ...> } ...> } ...> \""", - ...> false - ...> ) + ...> optimize: false, evm_version: "byzantium" + ...> ]) { :ok, %{ @@ -61,10 +61,18 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do } } """ - def run(name, compiler_version, code, optimize, evm_version \\ "byzantium", external_libs \\ %{}) do + def run(params) do + name = Keyword.fetch!(params, :name) + compiler_version = Keyword.fetch!(params, :compiler_version) + code = Keyword.fetch!(params, :code) + optimize = Keyword.fetch!(params, :optimize) + optimization_runs = params |> Keyword.get(:optimization_runs, 200) |> Integer.to_string() + evm_version = Keyword.get(params, :evm_version, List.last(@allowed_evm_versions)) + external_libs = Keyword.get(params, :external_libs, %{}) + external_libs_string = Jason.encode!(external_libs) - evm_version = + checked_evm_version = if evm_version in @allowed_evm_versions do evm_version else @@ -79,9 +87,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do code, compiler_version, optimize_value(optimize), + optimization_runs, @new_contract_name, external_libs_string, - evm_version + checked_evm_version ] ) diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index ae2dae5cbe..576b1fcef1 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -24,9 +24,18 @@ defmodule Explorer.SmartContract.Verifier do external_libraries = Map.get(params, "external_libraries", %{}) constructor_arguments = Map.get(params, "constructor_arguments", "") evm_version = Map.get(params, "evm_version", "byzantium") + optimization_runs = Map.get(params, "optimization_runs", 200) solc_output = - CodeCompiler.run(name, compiler_version, contract_source_code, optimization, evm_version, external_libraries) + CodeCompiler.run( + name: name, + compiler_version: compiler_version, + code: contract_source_code, + optimize: optimization, + optimization_runs: optimization_runs, + evm_version: evm_version, + external_libs: external_libraries + ) case compare_bytecodes(solc_output, address_hash, constructor_arguments) do {:error, :generated_bytecode} -> @@ -34,12 +43,12 @@ defmodule Explorer.SmartContract.Verifier do second_solc_output = CodeCompiler.run( - name, - compiler_version, - contract_source_code, - optimization, - next_evm_version, - external_libraries + name: name, + compiler_version: compiler_version, + code: contract_source_code, + optimize: optimization, + evm_version: next_evm_version, + external_libs: external_libraries ) compare_bytecodes(second_solc_output, address_hash, constructor_arguments) diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js index 2ed0ff2664..6e9489d433 100755 --- a/apps/explorer/priv/compile_solc.js +++ b/apps/explorer/priv/compile_solc.js @@ -5,9 +5,10 @@ const solc = require('solc'); var sourceCode = process.argv[2]; var version = process.argv[3]; var optimize = process.argv[4]; -var newContractName = process.argv[5]; -var externalLibraries = JSON.parse(process.argv[6]) -var evmVersion = process.argv[7] +var optimizationRuns = parseInt(process.argv[5], 10); +var newContractName = process.argv[6]; +var externalLibraries = JSON.parse(process.argv[7]) +var evmVersion = process.argv[8]; var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot) { if (err) { @@ -24,7 +25,7 @@ var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot) evmVersion: evmVersion, optimizer: { enabled: optimize == '1', - runs: 200 + runs: optimizationRuns }, libraries: { [newContractName]: externalLibraries diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index 3d6fd912cc..ecf1d71dfb 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -261,34 +261,36 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do end # Regression test for https://github.com/poanetwork/blockscout/issues/1644 - test "discards parent block if it isn't related to the current one because of reorg", + test "discards neighbouring blocks if they aren't related to the current one because of reorg and/or import timeout", %{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do - old_block = insert(:block, parent_hash: block_hash, number: block_number + 1) - insert(:block, parent_hash: old_block.hash, number: old_block.number + 1) + old_block1 = params_for(:block, miner_hash: miner_hash, parent_hash: block_hash, number: block_number + 1) - new_block1 = params_for(:block, parent_hash: block_hash, number: block_number + 1, miner_hash: miner_hash) + new_block1 = params_for(:block, miner_hash: miner_hash, parent_hash: block_hash, number: block_number + 1) + new_block2 = params_for(:block, miner_hash: miner_hash, parent_hash: new_block1.hash, number: block_number + 2) - new_block2 = - params_for(:block, parent_hash: new_block1.hash, number: new_block1.number + 1, miner_hash: miner_hash) + range = block_number..(block_number + 2) - %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block2) - changes_list = [block_changes] + insert_block(new_block1, options) + insert_block(new_block2, options) + assert Chain.missing_block_number_ranges(range) == [] - Multi.new() - |> Blocks.run(changes_list, options) - |> Repo.transaction() + insert_block(old_block1, options) + assert Chain.missing_block_number_ranges(range) == [(block_number + 2)..(block_number + 2)] - assert Chain.missing_block_number_ranges(block_number..new_block2.number) == [old_block.number..old_block.number] + insert_block(new_block2, options) + assert Chain.missing_block_number_ranges(range) == [(block_number + 1)..(block_number + 1)] - %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block1) - changes_list = [block_changes] + insert_block(new_block1, options) + assert Chain.missing_block_number_ranges(range) == [] + end + end - Multi.new() - |> Blocks.run(changes_list, options) - |> Repo.transaction() + defp insert_block(block_params, options) do + %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params) - assert Chain.missing_block_number_ranges(block_number..new_block2.number) == [] - end + Multi.new() + |> Blocks.run([block_changes], options) + |> Repo.transaction() end defp count(schema) do diff --git a/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs b/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs index 8ddcda2f04..a1b0d72485 100644 --- a/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs @@ -8,7 +8,7 @@ defmodule Explorer.Chain.TransactionCountCacheTest do result = TransactionCountCache.value(TestCache) - assert result == 0 + assert is_nil(result) end test "updates cache if initial value is zero" do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index c408716afd..ebb4b880dd 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3667,27 +3667,6 @@ defmodule Explorer.ChainTest do end end - describe "list_block_numbers_with_invalid_consensus/0" do - test "returns a list of block numbers with invalid consensus" do - block1 = insert(:block) - block2_with_invalid_consensus = insert(:block, parent_hash: block1.hash, consensus: false) - _block2 = insert(:block, parent_hash: block1.hash, number: block2_with_invalid_consensus.number) - block3 = insert(:block, parent_hash: block2_with_invalid_consensus.hash) - block4 = insert(:block, parent_hash: block3.hash) - block5 = insert(:block, parent_hash: block4.hash) - block6_without_consensus = insert(:block, parent_hash: block5.hash, consensus: false) - block6 = insert(:block, parent_hash: block5.hash, number: block6_without_consensus.number) - block7 = insert(:block, parent_hash: block6.hash) - block8_with_invalid_consensus = insert(:block, parent_hash: block7.hash, consensus: false) - _block8 = insert(:block, parent_hash: block7.hash, number: block8_with_invalid_consensus.number) - block9 = insert(:block, parent_hash: block8_with_invalid_consensus.hash) - _block10 = insert(:block, parent_hash: block9.hash) - - assert Chain.list_block_numbers_with_invalid_consensus() == - [block2_with_invalid_consensus.number, block8_with_invalid_consensus.number] - end - end - describe "block_combined_rewards/1" do test "sums the block_rewards values" do block = insert(:block) 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 ebf13a4d41..79bbb62f70 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 @@ -18,10 +18,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do test "compiles the latest solidity version", %{contract_code_info: contract_code_info} do response = CodeCompiler.run( - contract_code_info.name, - contract_code_info.version, - contract_code_info.source_code, - contract_code_info.optimized + name: contract_code_info.name, + compiler_version: contract_code_info.version, + code: contract_code_info.source_code, + optimize: contract_code_info.optimized, + evm_version: "byzantium" ) assert {:ok, @@ -37,10 +38,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do response = CodeCompiler.run( - contract_code_info.name, - contract_code_info.version, - contract_code_info.source_code, - optimize + name: contract_code_info.name, + compiler_version: contract_code_info.version, + code: contract_code_info.source_code, + optimize: optimize, + evm_version: "byzantium" ) assert {:ok, @@ -61,12 +63,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do {:ok, result} = CodeCompiler.run( - name, - compiler_version, - contract, - optimize, - "byzantium", - external_libraries + name: name, + compiler_version: compiler_version, + code: contract, + optimize: optimize, + evm_version: "byzantium", + external_libs: external_libraries ) clean_result = remove_init_data_and_whisper_data(result["bytecode"]) @@ -109,7 +111,14 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do evm_version = "constantinople" - response = CodeCompiler.run(name, version, code, optimize, evm_version) + response = + CodeCompiler.run( + name: name, + compiler_version: version, + code: code, + optimize: optimize, + evm_version: evm_version + ) assert {:ok, %{ @@ -139,7 +148,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do version = "v0.1.3-nightly.2015.9.25+commit.4457170" - response = CodeCompiler.run(name, version, code, optimize) + response = CodeCompiler.run(name: name, compiler_version: version, code: code, optimize: optimize) assert {:ok, %{ @@ -156,10 +165,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do response = CodeCompiler.run( - contract_code_info.name, - contract_code_info.version, - wrong_code, - contract_code_info.optimized + name: contract_code_info.name, + compiler_version: contract_code_info.version, + code: wrong_code, + optimize: contract_code_info.optimized ) assert {:error, :compilation} = response diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 9f9a34c18e..e95f3f421c 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -31,7 +31,7 @@ block_transformer = config :indexer, block_transformer: block_transformer, ecto_repos: [Explorer.Repo], - metadata_updater_days_interval: 7, + metadata_updater_days_interval: 2, # bytes memory_limit: 1 <<< 30, first_block: System.get_env("FIRST_BLOCK") || 0 diff --git a/apps/indexer/lib/indexer/block/invalid_consensus/supervisor.ex b/apps/indexer/lib/indexer/block/invalid_consensus/supervisor.ex deleted file mode 100644 index cca692a76c..0000000000 --- a/apps/indexer/lib/indexer/block/invalid_consensus/supervisor.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Indexer.Block.InvalidConsensus.Supervisor do - @moduledoc """ - Supervises process for ensuring blocks with invalid consensus get queued for - indexing. - """ - - use Supervisor - - alias Indexer.Block.InvalidConsensus.Worker - - def child_spec([]) do - child_spec([[]]) - end - - def child_spec([init_arguments]) do - child_spec([init_arguments, [name: __MODULE__]]) - end - - def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do - spec = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, - type: :supervisor - } - - Supervisor.child_spec(spec, []) - end - - def start_link(init_arguments, gen_server_options \\ []) do - Supervisor.start_link(__MODULE__, init_arguments, gen_server_options) - end - - @impl Supervisor - def init(_) do - children = [ - {Worker, [[supervisor: self()], [name: Worker]]}, - {Task.Supervisor, name: Indexer.Block.InvalidConsensus.TaskSupervisor} - ] - - opts = [strategy: :one_for_all] - - Supervisor.init(children, opts) - end -end diff --git a/apps/indexer/lib/indexer/block/invalid_consensus/worker.ex b/apps/indexer/lib/indexer/block/invalid_consensus/worker.ex deleted file mode 100644 index cbe93c604b..0000000000 --- a/apps/indexer/lib/indexer/block/invalid_consensus/worker.ex +++ /dev/null @@ -1,99 +0,0 @@ -defmodule Indexer.Block.InvalidConsensus.Worker do - @moduledoc """ - Finds blocks with invalid consensus and queues them up to be refetched. This - process does this once, after the application starts up. - - A block has invalid consensus when it is referenced as the parent hash of a - block with consensus while not having consensus (consensus=false). Only one - block can have consensus at a given height (block number). - """ - - use GenServer - - require Logger - - alias Explorer.Chain - alias Indexer.Block.Catchup.Fetcher - alias Indexer.Block.InvalidConsensus.TaskSupervisor - - def child_spec([init_arguments]) do - child_spec([init_arguments, []]) - end - - def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do - spec = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, - type: :worker - } - - Supervisor.child_spec(spec, []) - end - - def start_link(init_arguments, gen_server_options \\ []) do - GenServer.start_link(__MODULE__, init_arguments, gen_server_options) - end - - def init(opts) do - sup_pid = Keyword.fetch!(opts, :supervisor) - retry_interval = Keyword.get(opts, :retry_interval, 10_000) - - send(self(), :scan) - - state = %{ - block_numbers: [], - retry_interval: retry_interval, - sup_pid: sup_pid, - task_ref: nil - } - - {:ok, state} - end - - def handle_info(:scan, state) do - block_numbers = Chain.list_block_numbers_with_invalid_consensus() - - case block_numbers do - [] -> - Supervisor.stop(state.sup_pid, :normal) - {:noreply, state} - - block_numbers -> - Process.send_after(self(), :push_front_blocks, state.retry_interval) - {:noreply, %{state | block_numbers: block_numbers}} - end - end - - def handle_info(:push_front_blocks, %{block_numbers: block_numbers} = state) do - %Task{ref: ref} = async_push_front(block_numbers) - {:noreply, %{state | task_ref: ref}} - end - - def handle_info({ref, :ok}, %{task_ref: ref, sup_pid: sup_pid}) do - Process.demonitor(ref, [:flush]) - Supervisor.stop(sup_pid, :normal) - {:stop, :shutdown} - end - - def handle_info({ref, {:error, reason}}, %{task_ref: ref, retry_interval: millis} = state) do - case reason do - :queue_unavailable -> :ok - _ -> Logger.error(fn -> inspect(reason) end) - end - - Process.demonitor(ref, [:flush]) - Process.send_after(self(), :push_front_blocks, millis) - - {:noreply, %{state | task_ref: nil}} - end - - def handle_info({:DOWN, ref, :process, _, _}, %{task_ref: ref, retry_interval: millis} = state) do - Process.send_after(self(), :push_front_blocks, millis) - {:noreply, %{state | task_ref: nil}} - end - - defp async_push_front(block_numbers) do - Task.Supervisor.async_nolink(TaskSupervisor, Fetcher, :push_front, [block_numbers]) - end -end diff --git a/apps/indexer/lib/indexer/block/realtime/consensus_ensurer.ex b/apps/indexer/lib/indexer/block/realtime/consensus_ensurer.ex deleted file mode 100644 index 816f1d76bf..0000000000 --- a/apps/indexer/lib/indexer/block/realtime/consensus_ensurer.ex +++ /dev/null @@ -1,34 +0,0 @@ -defmodule Indexer.Block.Realtime.ConsensusEnsurer do - @moduledoc """ - Triggers a refetch if a given block doesn't have consensus. - - """ - require Logger - - alias Explorer.Chain - alias Explorer.Chain.Hash - alias Indexer.Block.Realtime.Fetcher - - def perform(_, number, _) when not is_integer(number) or number < 0, do: :ok - - def perform(%Hash{byte_count: unquote(Hash.Full.byte_count())} = block_hash, number, block_fetcher) do - case Chain.hash_to_block(block_hash) do - {:ok, %{consensus: true} = _block} -> - :ignore - - _ -> - Logger.info(fn -> - [ - "refetch from consensus was found on block (", - to_string(number), - "). A reorg initiated." - ] - end) - - # trigger refetch if consensus=false or block was not found - Fetcher.fetch_and_import_block(number, block_fetcher, true) - end - - :ok - end -end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 49787ad675..fe486f62c2 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -27,7 +27,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Explorer.Chain.TokenTransfer alias Explorer.Counters.AverageBlockTime alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer} - alias Indexer.Block.Realtime.{ConsensusEnsurer, TaskSupervisor} + alias Indexer.Block.Realtime.TaskSupervisor alias Timex.Duration @behaviour Block.Fetcher @@ -269,12 +269,7 @@ defmodule Indexer.Block.Realtime.Fetcher do @decorate span(tracer: Tracer) defp do_fetch_and_import_block(block_number_to_fetch, block_fetcher, retry) do case fetch_and_import_range(block_fetcher, block_number_to_fetch..block_number_to_fetch) do - {:ok, %{inserted: inserted, errors: []}} -> - for block <- Map.get(inserted, :blocks, []) do - args = [block.parent_hash, block.number - 1, block_fetcher] - Task.Supervisor.start_child(TaskSupervisor, ConsensusEnsurer, :perform, args) - end - + {:ok, %{inserted: _, errors: []}} -> Logger.debug("Fetched and imported.") {:ok, %{inserted: _, errors: [_ | _] = errors}} -> diff --git a/apps/indexer/lib/indexer/block/supervisor.ex b/apps/indexer/lib/indexer/block/supervisor.ex index 2bab786f18..50b24a6dd5 100644 --- a/apps/indexer/lib/indexer/block/supervisor.ex +++ b/apps/indexer/lib/indexer/block/supervisor.ex @@ -4,7 +4,7 @@ defmodule Indexer.Block.Supervisor do """ alias Indexer.Block - alias Indexer.Block.{Catchup, InvalidConsensus, Realtime, Reward, Uncle} + alias Indexer.Block.{Catchup, Realtime, Reward, Uncle} alias Indexer.Temporary.{AddressesWithoutCode, FailedCreatedAddresses} use Supervisor @@ -50,7 +50,6 @@ defmodule Indexer.Block.Supervisor do %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, [name: Catchup.Supervisor] ]}, - {InvalidConsensus.Supervisor, [[], [name: InvalidConsensus.Supervisor]]}, {Realtime.Supervisor, [ %{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments}, diff --git a/apps/indexer/lib/indexer/token/fetcher.ex b/apps/indexer/lib/indexer/token/fetcher.ex index c18f1d6488..818f27f138 100644 --- a/apps/indexer/lib/indexer/token/fetcher.ex +++ b/apps/indexer/lib/indexer/token/fetcher.ex @@ -52,11 +52,8 @@ defmodule Indexer.Token.Fetcher do @decorate trace(name: "fetch", resource: "Indexer.Token.Fetcher.run/2", service: :indexer, tracer: Tracer) def run([token_contract_address], _json_rpc_named_arguments) do case Chain.token_from_address_hash(token_contract_address) do - {:ok, %Token{cataloged: false} = token} -> + {:ok, %Token{} = token} -> catalog_token(token) - - {:ok, _} -> - :ok end end @@ -74,7 +71,7 @@ defmodule Indexer.Token.Fetcher do |> MetadataRetriever.get_functions_of() |> Map.put(:cataloged, true) - {:ok, _} = Chain.update_token(token, token_params) + {:ok, _} = Chain.update_token(%{token | updated_at: DateTime.utc_now()}, token_params) :ok end end diff --git a/apps/indexer/test/indexer/block/invalid_consensus/worker_test.exs b/apps/indexer/test/indexer/block/invalid_consensus/worker_test.exs deleted file mode 100644 index a6fa07f0d3..0000000000 --- a/apps/indexer/test/indexer/block/invalid_consensus/worker_test.exs +++ /dev/null @@ -1,87 +0,0 @@ -defmodule Indexer.Block.InvalidConsensus.WorkerTest do - use Explorer.DataCase - - alias Indexer.Sequence - alias Indexer.Block.InvalidConsensus.{Worker, TaskSupervisor} - - @moduletag :capture_log - - describe "start_link/1" do - test "starts the worker" do - assert {:ok, _pid} = Worker.start_link(supervisor: self()) - end - end - - describe "init/1" do - test "sends message to self" do - pid = self() - assert {:ok, %{task_ref: nil, block_numbers: [], sup_pid: ^pid}} = Worker.init(supervisor: self()) - assert_received :scan - end - end - - describe "handle_info with :scan" do - test "sends shutdown to supervisor" do - state = %{task_ref: nil, block_numbers: [], sup_pid: self()} - Task.async(fn -> Worker.handle_info(:scan, state) end) - assert_receive {_, _, {:terminate, :normal}} - end - - test "sends message to self when blocks with invalid consensus are found" do - block1 = insert(:block) - block2_with_invalid_consensus = insert(:block, parent_hash: block1.hash, consensus: false) - _block2 = insert(:block, parent_hash: block1.hash, number: block2_with_invalid_consensus.number) - _block3 = insert(:block, parent_hash: block2_with_invalid_consensus.hash) - - block_number = block2_with_invalid_consensus.number - - expected_state = %{task_ref: nil, block_numbers: [block_number], retry_interval: 1} - state = %{task_ref: nil, block_numbers: [], retry_interval: 1} - - assert {:noreply, ^expected_state} = Worker.handle_info(:scan, state) - assert_receive :push_front_blocks - end - end - - describe "handle_info with :push_front_blocks" do - test "starts a task" do - task_sup_pid = start_supervised!({Task.Supervisor, name: TaskSupervisor}) - start_supervised!({Sequence, [[ranges: [], step: -1], [name: :block_catchup_sequencer]]}) - - state = %{task_ref: nil, block_numbers: [1]} - assert {:noreply, %{task_ref: task_ref}} = Worker.handle_info(:push_front_blocks, state) - assert is_reference(task_ref) - - refute_receive {^task_ref, {:error, :queue_unavailable}} - assert_receive {^task_ref, :ok} - - stop_supervised(task_sup_pid) - end - end - - describe "handle_info with task ref tuple" do - test "sends shutdown to supervisor on success" do - ref = Process.monitor(self()) - state = %{task_ref: ref, block_numbers: [], sup_pid: self()} - Task.async(fn -> assert Worker.handle_info({ref, :ok}, state) end) - assert_receive {_, _, {:terminate, :normal}} - end - - test "sends message to self to try again on failure" do - ref = Process.monitor(self()) - state = %{task_ref: ref, block_numbers: [1], sup_pid: self(), retry_interval: 1} - expected_state = %{state | task_ref: nil} - assert {:noreply, ^expected_state} = Worker.handle_info({ref, {:error, :queue_unavailable}}, state) - assert_receive :push_front_blocks - end - end - - describe "handle_info with failed task" do - test "sends message to self to try again" do - ref = Process.monitor(self()) - state = %{task_ref: ref, block_numbers: [1], sup_pid: self(), retry_interval: 1} - assert {:noreply, %{task_ref: nil}} = Worker.handle_info({:DOWN, ref, :process, self(), :EXIT}, state) - assert_receive :push_front_blocks - end - end -end diff --git a/apps/indexer/test/indexer/token/fetcher_test.exs b/apps/indexer/test/indexer/token/fetcher_test.exs index 85872a70c5..d3e56d7712 100644 --- a/apps/indexer/test/indexer/token/fetcher_test.exs +++ b/apps/indexer/test/indexer/token/fetcher_test.exs @@ -20,12 +20,6 @@ defmodule Indexer.Token.FetcherTest do end describe "run/3" do - test "skips tokens that have already been cataloged", %{json_rpc_named_arguments: json_rpc_named_arguments} do - expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end) - %Token{contract_address_hash: contract_address_hash} = insert(:token, cataloged: true) - assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok - end - test "catalogs tokens that haven't been cataloged", %{json_rpc_named_arguments: json_rpc_named_arguments} do token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false) contract_address_hash = token.contract_address_hash