lib/etherex/time.ex

defmodule Etherex.Time do
  @moduledoc """
  Some Ethereum clients support jumping through time simulating time
  events like advance the time and the blocks or set and revert to a
  snapshot.

  Ganache-cli, for example, support the following non-standard JSON
  RPC methods:

  - evm_snapshot : Snapshot the state of the blockchain at the current
    block. Takes no parameters. Returns the integer id of the snapshot
    created. A snapshot can only be used (reverted) once.
  - evm_revert : Revert the state of the blockchain to a previous
    snapshot. Takes a single parameter, which is the snapshot id to
    revert to. This deletes the given snapshot, as well as any
    snapshots taken after.
  - evm_increaseTime : Jump forward in time. Takes one parameter,
    which is the amount of time to increase in seconds. Returns the
    total time adjustment, in seconds.
  - evm_mine : Force a block to be mined. Takes one optional
    parameter, which is the timestamp a block should setup as the
    mining time. Mines a block independent of whether or not mining is
    started or stopped.
  """

  require Logger
  alias Ethereumex.HttpClient, as: JsonRPC
  alias Etherex
  import Etherex.Helpers, only: [decode_json_rpc_error: 1, encode_quantity: 1, decode_quantity: 1]

  @doc """
  Forces a block to be mined. Takes one optional parameter, which is
  the timestamp a block should setup as the mining time. Returns the
  block mined.
  """
  @spec mine(integer() | nil) :: {:ok, Etherex.quantity()} | {:error, Etherex.error()}
  def mine(time \\ nil) do
    params = if is_nil(time), do: [], else: [encode_quantity(time)]

    case JsonRPC.request("evm_mine", params, []) do
      {:ok, _} ->
        block_number = Etherex.block_number!()
        Logger.info("Block #{block_number} mined")
        {:ok, block_number}

      error ->
        decode_json_rpc_error(error)
    end
  end

  @doc """
  Enables automatic mining.
  """
  @spec miner_start() :: :ok | {:error, Etherex.error()}
  def miner_start() do
    case JsonRPC.request("miner_start", [], []) do
      {:ok, _} -> :ok
      error -> decode_json_rpc_error(error)
    end
  end

  @doc """
  Disables automatic mining.
  """
  @spec miner_stop() :: :ok | {:error, Etherex.error()}
  def miner_stop() do
    case JsonRPC.request("miner_stop", [], []) do
      {:ok, _} -> :ok
      error -> decode_json_rpc_error(error)
    end
  end

  @spec snapshot() :: {:ok, non_neg_integer()} | {:error, Etherex.error()}
  def snapshot() do
    case JsonRPC.request("evm_snapshot", [], []) do
      {:ok, id} -> {:ok, decode_quantity(id)}
      error -> decode_json_rpc_error(error)
    end
  end

  @spec revert(id :: non_neg_integer()) :: :ok | {:error, Etherex.error()}
  def revert(id) do
    case JsonRPC.request("evm_revert", [encode_quantity(id)], []) do
      {:ok, _} -> :ok
      error -> decode_json_rpc_error(error)
    end
  end
end