lib/bsv/op_code.ex

defmodule BSV.OpCode do
  @moduledoc """
  Module for accessing Bitcoin Script Op Codes.

  Bitcoin Script provides a number of operations or commands, known as Op Codes.
  When the script is evaluated, the Op Codes manipulate the stack in some way.

  Within a script, an Op Code is single byte integer. Op Codes can also be
  referenced by an atom representing the word or name of the Op Code.
  """

  @typedoc """
  Op Code

  Represented as either an `t:atom/0` or an `t:integer/0`.
  """
  @type t() :: atom() | integer()

  @op_codes %{
    # push value
    OP_0: 0,
    OP_FALSE: 0,
    OP_PUSHDATA1: 76,
    OP_PUSHDATA2: 77,
    OP_PUSHDATA4: 78,
    OP_1NEGATE: 79,
    OP_RESERVED: 80,
    OP_TRUE: 81,
    OP_1: 81,
    OP_2: 82,
    OP_3: 83,
    OP_4: 84,
    OP_5: 85,
    OP_6: 86,
    OP_7: 87,
    OP_8: 88,
    OP_9: 89,
    OP_10: 90,
    OP_11: 91,
    OP_12: 92,
    OP_13: 93,
    OP_14: 94,
    OP_15: 95,
    OP_16: 96,

    # control
    OP_NOP: 97,
    OP_VER: 98,
    OP_IF: 99,
    OP_NOTIF: 100,
    OP_VERIF: 101,
    OP_VERNOTIF: 102,
    OP_ELSE: 103,
    OP_ENDIF: 104,
    OP_VERIFY: 105,
    OP_RETURN: 106,

    # stack ops
    OP_TOALTSTACK: 107,
    OP_FROMALTSTACK: 108,
    OP_2DROP: 109,
    OP_2DUP: 110,
    OP_3DUP: 111,
    OP_2OVER: 112,
    OP_2ROT: 113,
    OP_2SWAP: 114,
    OP_IFDUP: 115,
    OP_DEPTH: 116,
    OP_DROP: 117,
    OP_DUP: 118,
    OP_NIP: 119,
    OP_OVER: 120,
    OP_PICK: 121,
    OP_ROLL: 122,
    OP_ROT: 123,
    OP_SWAP: 124,
    OP_TUCK: 125,

    # splice ops
    OP_CAT: 126,
    OP_SPLIT: 127,
    OP_NUM2BIN: 128,
    OP_BIN2NUM: 129,
    OP_SIZE: 130,

    # bit logic
    OP_INVERT: 131,
    OP_AND: 132,
    OP_OR: 133,
    OP_XOR: 134,
    OP_EQUAL: 135,
    OP_EQUALVERIFY: 136,
    OP_RESERVED1: 137,
    OP_RESERVED2: 138,

    # numeric
    OP_1ADD: 139,
    OP_1SUB: 140,
    OP_2MUL: 141,
    OP_2DIV: 142,
    OP_NEGATE: 143,
    OP_ABS: 144,
    OP_NOT: 145,
    OP_0NOTEQUAL: 146,

    OP_ADD: 147,
    OP_SUB: 148,
    OP_MUL: 149,
    OP_DIV: 150,
    OP_MOD: 151,
    OP_LSHIFT: 152,
    OP_RSHIFT: 153,

    OP_BOOLAND: 154,
    OP_BOOLOR: 155,
    OP_NUMEQUAL: 156,
    OP_NUMEQUALVERIFY: 157,
    OP_NUMNOTEQUAL: 158,
    OP_LESSTHAN: 159,
    OP_GREATERTHAN: 160,
    OP_LESSTHANOREQUAL: 161,
    OP_GREATERTHANOREQUAL: 162,
    OP_MIN: 163,
    OP_MAX: 164,

    OP_WITHIN: 165,

    # crypto
    OP_RIPEMD160: 166,
    OP_SHA1: 167,
    OP_SHA256: 168,
    OP_HASH160: 169,
    OP_HASH256: 170,
    OP_CODESEPARATOR: 171,
    OP_CHECKSIG: 172,
    OP_CHECKSIGVERIFY: 173,
    OP_CHECKMULTISIG: 174,
    OP_CHECKMULTISIGVERIFY: 175,

    OP_CHECKLOCKTIMEVERIFY: 177,
    OP_CHECKSEQUENCEVERIFY: 178,

    # expansion
    OP_NOP1: 176,
    OP_NOP2: 177,
    OP_NOP3: 178,
    OP_NOP4: 179,
    OP_NOP5: 180,
    OP_NOP6: 181,
    OP_NOP7: 182,
    OP_NOP8: 183,
    OP_NOP9: 184,
    OP_NOP10: 185,

    # template matching params
    OP_SMALLDATA: 249,
    OP_SMALLINTEGER: 250,
    OP_PUBKEYS: 251,
    OP_PUBKEYHASH: 253,
    OP_PUBKEY: 254,
    OP_INVALIDOPCODE: 255
  }

  @doc """
  Returns a map of all Op Codes.
  """
  @spec all() :: map()
  def all(), do: @op_codes

  @doc """
  Returns an `t:atom/0` Op Code from the given value. Returns nil if the value
  is not a valid Op Code.

  ## Examples

      iex> BSV.OpCode.to_atom :OP_RETURN
      :OP_RETURN

      iex> BSV.OpCode.to_atom "op_return"
      :OP_RETURN

      iex> BSV.OpCode.to_atom <<106>>
      :OP_RETURN

      iex> BSV.OpCode.to_atom 106
      :OP_RETURN

      iex> BSV.OpCode.to_atom :UNKNOWN_CODE
      nil
  """
  @spec to_atom(atom() | binary() | String.t() | integer()) :: t() | nil
  def to_atom(op) when is_atom(op),
    do: Enum.find_value(@op_codes, fn {k, _v} -> if k == op, do: k end)

  def to_atom(<<op>>) when is_integer(op), do: to_atom(op)

  def to_atom(op) when is_binary(op),
    do: op |> String.upcase() |> String.to_existing_atom()

  def to_atom(0), do: :OP_FALSE

  def to_atom(op) when is_integer(op) and op in 0..255,
    do: Enum.find_value(@op_codes, fn {k, v} -> if v == op, do: k end)

  @doc """
  Returns an `t:atom/0` Op Code from the given value.

  As `to_atom/1` but raises an error if the value is not a valid Op Code.
  """
  @spec to_atom!(atom() | String.t() | binary() | integer()) :: t()
  def to_atom!(op) do
    case to_atom(op) do
      nil -> raise BSV.DecodeError, {:invalid_opcode, op}
      opcode -> opcode
    end
  end

  @doc """
  Returns an `t:integer/0` Op Code from the given value. Returns nil if the
  value is not a valid Op Code.

  ## Examples

      iex> BSV.OpCode.to_integer :OP_RETURN
      106

      iex> BSV.OpCode.to_integer "op_return"
      106

      iex> BSV.OpCode.to_integer <<106>>
      106

      iex> BSV.OpCode.to_integer 106
      106

      iex> BSV.OpCode.to_integer :UNKNOWN_CODE
      nil
  """
  @spec to_integer(atom() | binary() | String.t()) :: t() | nil
  def to_integer(op) when is_atom(op), do: @op_codes[op]

  def to_integer(<<op>>) when is_integer(op), do: op

  def to_integer(op) when is_binary(op),
    do: op |> String.upcase() |> String.to_existing_atom() |> to_integer()

  def to_integer(op) when is_integer(op) and op in 0..255,
    do: Enum.find_value(@op_codes, fn {_k, v} -> if v == op, do: v end)

  @doc """
  Returns an `t:integer/0` Op Code from the given value.

  As `to_integer/1` but raises an error if the value is not a valid Op Code.
  """
  @spec to_integer!(atom() | binary() | String.t()) :: t()
  def to_integer!(op) do
    case to_integer(op) do
      nil -> raise BSV.DecodeError, {:invalid_opcode, op}
      opcode -> opcode
    end
  end

end