defmodule Solidity.Watcher do @otp_app :solidity_watcher @solidity_repo_url "https://github.com/ethereum/solidity" @contracts_path Application.compile_env(@otp_app, :contracts_path, "contracts/*.sol") @output_path Application.compile_env(@otp_app, :output_path, "solidity_build") @solidity_version Application.compile_env(@otp_app, :version, "latest") @existing_exec_path System.find_executable("solc") require Logger def install_and_run, do: install_and_run(:default, []) def install_and_run(profile, args) when is_atom(profile) and is_list(args) do executable_path = @existing_exec_path if is_nil(executable_path) do install() end contracts_formatted_path = "/#{Path.absname(@contracts_path) |> String.split("/") |> Enum.drop(-1) |> Enum.drop(1) |> Enum.join("/")}" :fs.start_link( @otp_app, contracts_formatted_path ) :fs.subscribe(@otp_app) run() wait_for_reload() end def wait_for_reload do receive do {_watcher_process, {:fs, :file_event}, {changed_file, _type}} -> Logger.info("#{changed_file} was updated. Recompiling...") run() wait_for_reload() end end def run do build_command = "#{bin_path()} --abi --bin --overwrite -o #{@output_path} #{@contracts_path}" build_command |> System.shell() |> elem(0) |> String.trim() |> Logger.info() end def install do version = @solidity_version distro = :os.type() |> elem(1) |> format_distro_name {version, distro} |> build_download_url |> download_release end defp download_release({_, _, download_url}) do binary = fetch_body!(download_url) File.mkdir_p!(Path.dirname(bin_path())) File.write!(bin_path(), binary, [:binary]) File.chmod(bin_path(), 0o755) end defp fetch_body!(url) do url = String.to_charlist(url) Logger.debug("Downloading Solidity Compiler from #{url}") {:ok, _} = Application.ensure_all_started(:inets) {:ok, _} = Application.ensure_all_started(:ssl) if proxy = System.get_env("HTTP_PROXY") || System.get_env("http_proxy") do Logger.debug("Using HTTP_PROXY: #{proxy}") %{host: host, port: port} = URI.parse(proxy) :httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}]) end if proxy = System.get_env("HTTPS_PROXY") || System.get_env("https_proxy") do Logger.debug("Using HTTPS_PROXY: #{proxy}") %{host: host, port: port} = URI.parse(proxy) :httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}]) end # https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets cacertfile = CAStore.file_path() |> String.to_charlist() http_options = [ ssl: [ verify: :verify_peer, cacertfile: cacertfile, depth: 2, customize_hostname_check: [ match_fun: :public_key.pkix_verify_hostname_match_fun(:https) ] ] ] options = [body_format: :binary] case :httpc.request(:get, {url, []}, http_options, options) do {:ok, {{_, 200, _}, _headers, body}} -> body other -> raise "couldn't fetch #{url}: #{inspect(other)}" end end defp build_download_url({version, distro}) do {version, distro, "#{@solidity_repo_url}/releases/#{version}/download/solc-#{distro}"} end defp format_distro_name(distro) do case distro do :darwin -> "macos" :windows -> "windows.exe" :linux -> "static-linux" _ -> "static-linux" end end defp bin_path do Application.get_env(@otp_app, :solc_path) || @existing_exec_path || if Code.ensure_loaded?(Mix.Project) do Path.join( Path.dirname(Mix.Project.build_path()), "solidity-#{@solidity_version}" ) else Path.expand("_build/solidity-#{@solidity_version}") end end end