DiodeClient.Anvil.Helper (Diode Client v1.4.8)

Copy Markdown View Source

Helper to download and deploy diode_contract (https://github.com/diodechain/diode_contract) on a local Anvil chain for testing.

Requires Foundry (forge) on PATH for contract deployment. Set ANVIL_CONTRACT_REPO_PATH to use an existing clone; otherwise the repo is cloned to a temporary directory.

One-shot test env (for downstream test_helper.exs)

# In your app's test/test_helper.exs:
case DiodeClient.Anvil.Helper.ensure_test_env(wallet: "test_anvil", deploy_contracts: false) do
  :ok ->
    :ok
  {:error, :anvil_not_reachable} ->
    ExUnit.configure(exclude: [anvil: true])
  {:error, _reason} ->
    ExUnit.configure(exclude: [anvil: true])
end
ExUnit.start()

Example (manual steps)

# With Anvil running (e.g. anvil) and diode_contract deployed:
{:ok, list, addresses} = DiodeClient.Anvil.Helper.deploy_contracts()
DiodeClient.Contracts.Factory.set_anvil_contracts(list)

Summary

Functions

Returns whether the Anvil RPC endpoint is reachable (e.g. anvil is running).

Runs forge build in the given repo path. Returns :ok or {:error, reason}.

Deploys contracts from diode_contract to the Anvil RPC URL (default from ANVIL_RPC_URL).

Ensures the diode_contract repo is available: returns path from ANVIL_CONTRACT_REPO_PATH or clones to a temporary directory. Returns {:ok, path} or {:error, reason}.

One-shot setup for using the Anvil shell in tests.

Returns the path to the diode_contract repo. Uses ANVIL_CONTRACT_REPO_PATH if set; otherwise returns nil (caller must clone or set path).

Spawns Anvil in the background and waits until the RPC endpoint is reachable.

Stops the Anvil process (if running) and clears the stored port.

Functions

anvil_reachable?(rpc_url \\ nil)

Returns whether the Anvil RPC endpoint is reachable (e.g. anvil is running).

Use in test_helper.exs to exclude :anvil tests when Anvil is not running:

if not DiodeClient.Anvil.Helper.anvil_reachable?() do
  ExUnit.configure(exclude: [anvil: true])
end

Optional rpc_url defaults to ANVIL_RPC_URL or http://127.0.0.1:8545.

build_repo(path)

Runs forge build in the given repo path. Returns :ok or {:error, reason}.

deploy_contracts(rpc_url \\ nil, opts \\ [])

Deploys contracts from diode_contract to the Anvil RPC URL (default from ANVIL_RPC_URL).

Option A: If a deploy script exists (e.g. script/DeployAnvil.s.sol), run it and parse broadcast output. Option B: Read out/ artifacts and send deployment transactions.

Returns {:ok, %List{}, addresses} where addresses is a map of contract name => deployed address (<<_::160>>). Calls Factory.set_anvil_contracts(list) so Factory.contracts(DiodeClient.Shell.Anvil) works. Returns {:error, reason} on failure.

ensure_repo()

Ensures the diode_contract repo is available: returns path from ANVIL_CONTRACT_REPO_PATH or clones to a temporary directory. Returns {:ok, path} or {:error, reason}.

ensure_test_env(opts \\ [])

One-shot setup for using the Anvil shell in tests.

Call from your library's test/test_helper.exs before ExUnit.start(). Optionally sets a wallet, checks Anvil reachability, and deploys diode_contract so DiodeClient.Contracts.Factory.contracts(DiodeClient.Shell.Anvil) works.

Options

  • :wallet – Path or callback for setting the wallet. If set, ensures a wallet is set (e.g. "test_anvil" creates/uses that file). Skip if your tests do not send transactions. Default: no change.
  • :deploy_contracts – If true, ensures diode_contract repo is available, runs forge build, deploys contracts to Anvil, and calls Factory.set_anvil_contracts/1. Requires Anvil reachable and Foundry on PATH. Default: false.
  • :rpc_url – Anvil RPC URL. Default: ANVIL_RPC_URL or http://127.0.0.1:8545.

Returns

  • :ok – Wallet (if requested) and optional deployment succeeded.
  • {:error, :anvil_not_reachable} – RPC endpoint not reachable; exclude :anvil tests or start Anvil.
  • {:error, reason} – Deployment or wallet error (e.g. {:clone_failed, _}).

Example (minimal: Anvil only, no contracts)

DiodeClient.Anvil.Helper.ensure_test_env(wallet: "test_anvil")
ExUnit.start()

Example (Anvil + contracts, skip anvil tests when unreachable)

case DiodeClient.Anvil.Helper.ensure_test_env(wallet: "test_anvil", deploy_contracts: true) do
  :ok -> :ok
  {:error, :anvil_not_reachable} -> ExUnit.configure(exclude: [anvil: true])
  {:error, _} -> ExUnit.configure(exclude: [anvil: true])
end
ExUnit.start()

mine(rpc_url \\ nil)

post(url, body, timeout)

post(url, body, timeout, retries, last_error)

repo_path()

Returns the path to the diode_contract repo. Uses ANVIL_CONTRACT_REPO_PATH if set; otherwise returns nil (caller must clone or set path).

start_anvil(opts \\ [])

Spawns Anvil in the background and waits until the RPC endpoint is reachable.

Use in test_helper.exs so mix test works without manually starting Anvil:

case DiodeClient.Anvil.Helper.start_anvil() do
  {:ok, _port} -> :ok
  {:error, _} -> ExUnit.configure(exclude: [anvil: true])
end
DiodeClient.Anvil.Helper.ensure_test_env(wallet: "test_anvil")
ExUnit.start()

The process is kept alive for the test run (port stored in process); when the test runner exits, Anvil is terminated.

Options

  • :rpc_url – URL to poll (default: ANVIL_RPC_URL or http://127.0.0.1:8545).
  • :timeout – Max ms to wait for Anvil to become reachable (default: 15_000).
  • :port – Port for Anvil (default: parsed from :rpc_url or 8545).
  • :args – Extra args for anvil (default: []).

Returns

  • {:ok, port} – Anvil is running and reachable.
  • {:error, :executable_not_found}anvil not on PATH (install Foundry).
  • {:error, :timeout} – Anvil did not become reachable within :timeout.
  • {:error, {:spawn_failed, reason}} – Failed to start the process.

stop_anvil(port)

Stops the Anvil process (if running) and clears the stored port.

Returns :ok. Safe to call when Anvil was not started by this helper.