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