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

Commit

Permalink
Added support for simple arrays (#182)
Browse files Browse the repository at this point in the history
* Added support for simple arrays

* Added support for complex types
  • Loading branch information
ElFantasma authored Sep 22, 2023
1 parent 1eb4dd1 commit b2ea656
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 65 deletions.
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
2 changes: 1 addition & 1 deletion lib/starknet_explorer_web/live/transaction_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,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 @@ -68,6 +68,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 @@ -88,7 +99,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

0 comments on commit b2ea656

Please sign in to comment.