defmodule Tezex.Micheline do
@moduledoc """
Serialize/deserialize data to/from Tezos Micheline optimized binary representation.
"""
alias Tezex.Forge
alias Tezex.Zarith
@type pack_types :: nil | :address | :bytes | :int | :key_hash | :nat | :string
@doc """
Serialize a piece of data to its optimized binary representation.
## Examples
iex> pack(-6407, :int)
"0500c764"
iex> pack(%{"prim" => "Pair", "args" => [%{"int" => "-33"}, %{"int" => "71"}]})
"0507070061008701"
"""
@spec pack(binary() | integer() | map(), pack_types()) :: nonempty_binary()
@spec pack(binary() | integer() | map()) :: nonempty_binary()
def pack(value, type \\ nil) do
case type do
:int ->
"0500" <> Zarith.encode(value)
:nat ->
"0500" <> Zarith.encode(value)
:string ->
hex_bytes = Base.encode16(value, case: :lower)
"0501" <> encode_byte_size(hex_bytes) <> hex_bytes
:bytes ->
hex_bytes = Base.encode16(value, case: :lower)
"050a#{encode_byte_size(hex_bytes)}#{hex_bytes}"
:key_hash ->
address = Forge.forge_address(value, :hex)
"050a#{encode_byte_size(address)}#{address}"
:address ->
address = Forge.forge_address(value, :hex)
"050a#{encode_byte_size(address)}#{address}"
nil ->
"05" <> Forge.forge_micheline(value, :hex)
end
end
@doc """
Deserialize a piece of data from its optimized binary representation.
## Examples
iex> unpack("050a0000001601e67bac124dff100a57644de0cf26d341ebf9492600", :address)
"KT1VbT8n6YbrzPSjdAscKfJGDDNafB5yHn1H"
iex> unpack("0507070001000c")
%{"prim" => "Pair", "args" => [%{"int" => "1"}, %{"int" => "12"}]}
"""
@type unpack_result :: binary() | integer() | map() | list(unpack_result())
@spec unpack(binary(), pack_types()) :: unpack_result()
@spec unpack(binary()) :: unpack_result()
def unpack(hex_value, type \\ nil) do
case type do
:int ->
hex_value
|> binary_slice(4..-1//1)
|> Forge.unforge_int(:hex)
|> elem(0)
:nat ->
hex_value
|> binary_slice(4..-1//1)
|> Forge.unforge_int(:hex)
|> elem(0)
:string ->
hex_value
|> binary_slice(12..-1//1)
|> :binary.decode_hex()
:bytes ->
hex_value
|> binary_slice(12..-1//1)
:key_hash ->
hex_value
|> binary_slice(12..-1//1)
|> Forge.unforge_address(:hex)
:address ->
hex_value
|> binary_slice(12..-1//1)
|> Forge.unforge_address(:hex)
nil ->
hex_value
|> binary_slice(2..-1//1)
|> Forge.unforge_micheline(:hex)
end
end
defp encode_byte_size(bytes) do
(byte_size(bytes) / 2)
|> trunc()
|> Integer.to_string(16)
|> String.pad_leading(8, "0")
end
end