Current section

Files

Jump to
signet lib signet sleuth.ex
Raw

lib/signet/sleuth.ex

defmodule Signet.Sleuth do
@moduledoc ~S"""
Sleuth allows you to run a contract call as a single
`eth_call` call.
Note: Signet.Contract.Sleuth generated from `mix signet.gen --prefix signet/contract ./priv/Sleuth.json`
"""
use Signet.Hex
@sleuth_address ~h[0xFd946Bf25C47A1Bff567B28bA78a961bf78FF9d2]
def query(bytecode, query, selector, opts \\ []),
do: query_internal(bytecode, query, selector, false, opts)
def query_annotated(bytecode, query, selector, opts \\ []),
do: query_internal(bytecode, query, selector, true, opts)
def query_by(mod, fun) when is_atom(mod) and is_atom(fun), do: query_by(mod, fun, [])
def query_by(mod, opts) when is_atom(mod) and is_list(opts), do: query_by(mod, :query, opts)
def query_by(mod), do: query_by(mod, :query, [])
def query_by(mod, fun, opts) when is_atom(mod) and is_atom(fun) and is_list(opts) do
bytecode = try_apply(mod, :bytecode, [])
query_val = try_apply(mod, String.to_atom("encode_" <> to_string(fun)), [])
selector = try_apply(mod, String.to_atom(to_string(fun) <> "_selector"), [])
query_internal(bytecode, query_val, selector, false, opts)
end
defp query_internal(bytecode, query, selector, annotated, opts)
when is_binary(bytecode) and is_list(opts) do
{sleuth_address, opts} = Keyword.pop(opts, :sleuth_address, @sleuth_address)
{decode_binaries, rpc_opts} = Keyword.pop(opts, :decode_binaries, true)
with {:ok, query_res_bytes} <-
Signet.Contract.Sleuth.call_query(sleuth_address, bytecode, query, rpc_opts),
{:ok, query_res} <- try_decode_bytes(query_res_bytes),
{:ok, res} <- try_decode(query_res, selector) do
{:ok, unwrap_outer_tuple(encode_map(res, selector.returns, annotated, decode_binaries))}
end
end
defp try_decode_bytes(bytes) do
try do
[decoded] = ABI.decode("(bytes)", bytes)
{:ok, decoded}
rescue
e ->
{:error, "error decoding bytes: #{inspect(e)}"}
end
end
defp try_decode(query_res, selector) do
try do
{:ok, ABI.decode(%ABI.FunctionSelector{types: selector.returns}, query_res)}
rescue
e ->
{:error, "error decoding: #{inspect(e)}"}
end
end
defp encode_map(res, types, annotated, decode_binaries) do
Enum.map(Enum.zip(res, Enum.with_index(types)), fn {res, {type, i}} ->
encode_item(res, type, i, annotated, decode_binaries)
end)
|> Enum.into(%{})
end
defp encode_item(res, type, i, annotated, decode_binaries) do
var_name =
if is_nil(type.name) or type.name == "" do
"var#{i}"
else
type.name
end
res_enc = encode_value(res, type.type, annotated, decode_binaries)
{var_name, res_enc}
end
defp encode_value(res, type, annotated, decode_binaries) do
encode_array = fn sub_type ->
Enum.map(res, fn r -> encode_value(r, sub_type, annotated, decode_binaries) end)
end
case type do
{:tuple, sub_types} ->
encode_map(Tuple.to_list(res), sub_types, annotated, decode_binaries)
{:array, sub_type} ->
encode_array.(sub_type)
{:array, sub_type, _} ->
encode_array.(sub_type)
_ ->
res_enc =
case {type, decode_binaries} do
{:address, false} ->
to_hex(res)
{:bytes, false} ->
to_hex(res)
{{:bytes, _size}, false} ->
to_hex(res)
_ ->
res
end
if annotated do
{type, res_enc}
else
res_enc
end
end
end
defp unwrap_outer_tuple(xs = %{"var0" => x}) when map_size(xs) == 1, do: x
defp unwrap_outer_tuple(els), do: els
defp try_apply(mod, fun, args) do
try do
apply(mod, fun, args)
rescue
_ ->
raise "Sleuth module #{mod} does not define required \"#{fun}/#{Enum.count(args)}\" function"
end
end
end