Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Added support for simple arrays #182

Merged
merged 2 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 154 additions & 62 deletions lib/starknet_explorer/calldata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ defmodule StarknetExplorer.Calldata do
Enum.map(
calldata,
fn call ->
input = get_input_data(block_id, call.address, call.selector, network)
Map.put(call, :call, as_fn_call(input, call.calldata))
functions_data = get_functions_data(block_id, call.address, network)
{function, structs} = get_input_data(functions_data, call.selector)

Map.put(call, :call, as_fn_call(function, call.calldata, structs))
end
)
end
Expand Down Expand Up @@ -91,54 +93,109 @@ defmodule StarknetExplorer.Calldata do
("0x" <> Integer.to_string(result, 16)) |> String.downcase()
end

def as_fn_call(nil, _calldata) do
def as_fn_call(nil, _calldata, _structs) do
nil
end

def as_fn_call(input, calldata) do
%{:name => input["name"], :args => as_fn_inputs(input["inputs"], calldata)}
def as_fn_call(function, calldata, structs) do
{inputs, _} = as_fn_inputs(function["inputs"], calldata, structs)
%{:name => function["name"], :args => inputs}
end

def as_fn_inputs(inputs, calldata) do
{result, _} =
def as_fn_inputs(inputs, calldata, structs) do
{result, rest} =
List.foldl(
inputs,
{[], calldata},
fn input, {acc_current, acc_calldata} ->
{fn_input, calldata_rest} = as_fn_input(input, acc_calldata)
{[fn_input | acc_current], calldata_rest}
fn input, {so_far, acc_calldata} ->
{fn_input, calldata_rest} = as_fn_input(input, so_far, acc_calldata, structs)
{[fn_input | so_far], calldata_rest}
end
)

Enum.reverse(result)
{Enum.reverse(result), rest}
end

def as_fn_input(input, calldata) do
{value, calldata_rest} = get_value_for_type(input["type"], calldata)
def as_fn_input(input, so_far, calldata, structs) do
{value, calldata_rest} = get_value_for_type(input, so_far, calldata, structs)
{%{:name => input["name"], :type => input["type"], :value => value}, calldata_rest}
end

def get_value_for_type("Uint256", [value1, value2 | rest]) do
def get_value_for_type(
%{"type" => "core::array::Array::<" <> inner_type},
_so_far,
[
array_length | rest
],
structs
) do
inner_type = String.replace_suffix(inner_type, ">", "")
get_multiple_values_for_type(felt_to_int(array_length), inner_type, rest, structs)
end

def get_value_for_type(%{"type" => type, "name" => name}, so_far, calldata, structs) do
case String.ends_with?(type, "*") do
true ->
type = String.replace_suffix(type, "*", "!")
get_multiple_values_for_type(get_array_len(name, so_far), type, calldata, structs)

_ ->
get_value_for_single_type(type, calldata, structs)
end
end

def get_value_for_single_type("Uint256", [value1, value2 | rest], _structs) do
{[value2, value1], rest}
end

def get_value_for_type("core::integer::u256", [value1, value2 | rest]) do
def get_value_for_single_type("core::integer::u256", [value1, value2 | rest], _structs) do
{[value2, value1], rest}
end

def get_value_for_type(_, [value | rest]) do
{value, rest}
def get_value_for_single_type(type, [value | rest], structs) do
case Map.get(structs, type) do
nil ->
{value, rest}

%{"members" => members} ->
get_value_for_complex_type(members, [value | rest], structs)
end
end

def get_multiple_values_for_type(0, _type, list, _structs) do
{[], list}
end

def get_multiple_values_for_type(n, type, list, structs) do
{value, rest} = get_value_for_single_type(type, list, structs)
{values, final_rest} = get_multiple_values_for_type(n - 1, type, rest, structs)
{[value | values], final_rest}
end

def get_value_for_complex_type(members, list, structs) do
as_fn_inputs(members, list, structs)
end

def get_array_len(name, input_data) do
Enum.find(
input_data,
fn %{:name => input_name} ->
name <> "_len" == input_name
end
)
|> Map.get(:value)
|> felt_to_int
end

def felt_to_int(<<"0x", hexa_value::binary>>) do
{value, _} = Integer.parse(hexa_value, 16)
value
end

def get_input_data(block_id, address, selector, network) do
def get_functions_data(block_id, address, network) do
case Rpc.get_class_at(block_id, address, network) do
{:ok, class} ->
selectors = parse_class(class)
selectors = get_selectors(class)

implementation_selector =
Enum.find(
Expand All @@ -148,31 +205,65 @@ defmodule StarknetExplorer.Calldata do
end
)

case implementation_selector do
nil ->
find_by_selector(class, selector)
implementation_data =
case implementation_selector do
nil ->
nil

_ ->
{:ok, [implementation_address_or_hash]} =
Rpc.call(block_id, address, implementation_selector, network)
_ ->
{:ok, [implementation_address_or_hash]} =
Rpc.call(block_id, address, implementation_selector, network)

case Rpc.get_class(block_id, implementation_address_or_hash, network) do
{:ok, class} ->
find_by_selector(class, selector)
case Rpc.get_class(block_id, implementation_address_or_hash, network) do
{:ok, class} ->
process_class_data(class, implementation_address_or_hash, :hash)

{:error, _error} ->
{:ok, class} = Rpc.get_class_at(block_id, implementation_address_or_hash, network)
find_by_selector(class, selector)
end
end
{:error, _error} ->
{:ok, class} =
Rpc.get_class_at(block_id, implementation_address_or_hash, network)

process_class_data(class, implementation_address_or_hash, :address)
end
end

%{
:main => process_class_data(class, address, :address),
:implementation => implementation_data
}

{:error, error} ->
error |> IO.inspect()
nil
end
end

def parse_class(class) do
def process_class_data(class, id, id_type) do
class = normalize_abi(class)
{functions, structs} = functions_by_selector(class)

%{
:id => id,
:id_type => id_type,
:class => class,
:functions_by_selector => functions,
:structs_by_name => structs
}
end

def get_input_data(functions_data, selector) do
{functions, structs} =
case functions_data.implementation do
nil ->
{functions_data.main.functions_by_selector, functions_data.main.structs_by_name}

implementation ->
{implementation.functions_by_selector, implementation.structs_by_name}
end

{functions[selector], structs}
end

def get_selectors(class) do
List.foldl(
class["entry_points_by_type"]["EXTERNAL"],
MapSet.new(),
Expand All @@ -182,18 +273,35 @@ defmodule StarknetExplorer.Calldata do
)
end

def get_input_data_for_hash(block_id, class_hash, selector, network) do
case Rpc.get_class(block_id, class_hash, network) do
{:ok, class} ->
find_by_selector(class, selector)
def functions_by_selector(class) do
functions_by_selector_and_version(class.abi, class["contract_class_version"])
end

{:error, error} ->
error |> IO.inspect()
nil
end
def functions_by_selector_and_version(abi, nil) do
{abi
|> List.foldl(%{}, &Map.put(&2, &1["name"] |> keccak(), &1)), %{}}
end

# we assume contract_class_version 0.1.0
def functions_by_selector_and_version(abi, _contract_class_version) do
abi
|> List.foldl(
{%{}, %{}},
fn
elem = %{"type" => "interface"}, {fn_acc, struct_acc} ->
{List.foldl(elem["items"], fn_acc, &Map.put(&2, &1["name"] |> keccak(), &1)),
struct_acc}

elem = %{"type" => "struct"}, {fn_acc, struct_acc} ->
{fn_acc, Map.put(struct_acc, elem["name"], elem)}

_elem, {fn_acc, struct_acc} ->
{fn_acc, struct_acc}
end
)
end

def find_by_selector(class, selector) do
def normalize_abi(class) do
abi =
case class["abi"] do
abi when is_binary(abi) ->
Expand All @@ -203,24 +311,8 @@ defmodule StarknetExplorer.Calldata do
abi
end

find_by_selector_and_version(abi, class["contract_class_version"], selector)
end

def find_by_selector_and_version(abi, nil, selector) do
Enum.find(
abi,
fn elem ->
elem["name"] |> keccak() == selector
end
)
end

# we assume contract_class_version 0.1.0
def find_by_selector_and_version(abi, _contract_class_version, selector) do
abi
|> Enum.map(& &1["items"])
|> Enum.filter(& &1)
|> Enum.concat()
|> Enum.find(&(&1["name"] |> keccak() == selector))
class
|> Map.delete("abi")
|> Map.put(:abi, abi)
end
end
4 changes: 2 additions & 2 deletions lib/starknet_explorer_web/live/transaction_live.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule StarknetExplorerWeb.TransactionLive do
use StarknetExplorerWeb, :live_view
alias StarknetExplorerWeb.Utils
alias StarknetExplorer.{Data, Message, Rpc, BlockUtils}
alias StarknetExplorer.{Data, Message, Rpc}

defp transaction_header(assigns) do
~H"""
Expand Down Expand Up @@ -502,7 +502,7 @@ defmodule StarknetExplorerWeb.TransactionLive do
<div>
<div class="list-h">Value</div>
<div class="break-all">
<%= Utils.format_arg_value(arg) %>
<pre><%= Utils.format_arg_value(arg) %></pre>
</div>
</div>
</div>
Expand Down
35 changes: 33 additions & 2 deletions lib/starknet_explorer_web/live/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ defmodule StarknetExplorerWeb.Utils do
shorten_block_hash(value)
end

def format_arg_value(%{:type => "core::felt252", :value => value}) do
shorten_block_hash(value)
end

def format_arg_value(%{
:type => "core::starknet::contract_address::ContractAddress",
:value => value
}) do
shorten_block_hash(value)
end

def format_arg_value(%{
:type => "Uint256",
:value => [<<"0x", high::binary>>, <<"0x", low::binary>>]
Expand All @@ -86,7 +97,27 @@ defmodule StarknetExplorerWeb.Utils do
|> String.downcase())
end

def format_arg_value(%{:value => value}) do
shorten_block_hash(value)
def format_arg_value(%{
:type => "core::array::Array::<" <> _rest,
:value => value
}) do
value
|> Jason.encode!()
|> Jason.Formatter.pretty_print()
end

def format_arg_value(%{:type => type, :value => value}) do
case String.ends_with?(type, "*") do
true ->
type = String.replace_suffix(type, "*", "")

value
|> Enum.map(&format_arg_value(%{:type => type, :value => &1}))
|> Jason.encode!()
|> Jason.Formatter.pretty_print()

_ ->
value
end
end
end