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