Blockchain explorer for Ethereum based network and a tool for inspecting and analyzing EVM based blockchains.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
blockscout/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex

111 lines
3.5 KiB

defmodule EthereumJSONRPC.Encoder do
@moduledoc """
Deals with encoding and decoding data to be sent to, or that is
received from, the blockchain.
"""
alias ABI.TypeDecoder
@doc """
Given an ABI and a set of functions, returns the data the blockchain expects.
"""
@spec encode_abi([map()], %{String.t() => [any()]}) :: map()
def encode_abi(abi, functions) do
abi
|> ABI.parse_specification()
|> get_selectors(functions)
|> Enum.map(&encode_function_call/1)
|> Map.new()
end
@doc """
Given a list of function selectors from the ABI lib, and a list of functions names with their arguments, returns a list of selectors with their functions.
"""
@spec get_selectors([%ABI.FunctionSelector{}], %{String.t() => [term()]}) :: [{%ABI.FunctionSelector{}, [term()]}]
def get_selectors(abi, functions) do
Enum.map(functions, fn {function_name, args} ->
{get_selector_from_name(abi, function_name), args}
end)
end
@doc """
Given a list of function selectors from the ABI lib, and a function name, get the selector for that function.
"""
@spec get_selector_from_name([%ABI.FunctionSelector{}], String.t()) :: %ABI.FunctionSelector{}
def get_selector_from_name(abi, function_name) do
Enum.find(abi, fn selector -> function_name == selector.function end)
end
@doc """
Given a function selector and a list of arguments, returns their encoded versions.
This is what is expected on the Json RPC data parameter.
"""
@spec encode_function_call({%ABI.FunctionSelector{}, [term()]}) :: {String.t(), String.t()}
def encode_function_call({function_selector, args}) do
encoded_args =
function_selector
|> ABI.encode(parse_args(args))
|> Base.encode16(case: :lower)
{function_selector.function, "0x" <> encoded_args}
end
defp parse_args(args) do
args
|> Enum.map(fn
<<"0x", hexadecimal_digits::binary>> ->
Base.decode16!(hexadecimal_digits, case: :mixed)
item ->
item
end)
end
@doc """
Given a result set from the blockchain, and the functions selectors, returns the results decoded.
This functions assumes the result["id"] is the name of the function the result is for.
"""
@spec decode_abi_results([map()], [map()], %{String.t() => [any()]}) :: map()
def decode_abi_results(results, abi, functions) do
selectors =
abi
|> ABI.parse_specification()
|> get_selectors(functions)
|> Enum.map(fn {selector, _args} -> selector end)
results
|> Stream.map(&join_result_and_selector(&1, selectors))
|> Stream.map(&decode_result/1)
|> Map.new()
end
defp join_result_and_selector(result, selectors) do
{result, Enum.find(selectors, &(&1.function == result[:id]))}
end
@doc """
Given a result from the blockchain, and the function selector, returns the result decoded.
"""
@spec decode_result({map(), %ABI.FunctionSelector{}}) ::
{String.t(), {:ok, any()} | {:error, String.t() | :invalid_data}}
def decode_result({%{error: %{code: code, message: message}, id: id}, _selector}) do
{id, {:error, "(#{code}) #{message}"}}
end
def decode_result({%{id: id, result: result}, function_selector}) do
types_list = List.wrap(function_selector.returns)
decoded_data =
result
|> String.slice(2..-1)
|> Base.decode16!(case: :lower)
|> TypeDecoder.decode_raw(types_list)
{id, {:ok, decoded_data}}
rescue
MatchError ->
{id, {:error, :invalid_data}}
end
end