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

Commit

Permalink
Input data (#142)
Browse files Browse the repository at this point in the history
* First attemp to complete input data

* Masking some errors for unsupported transactions

* Removed commented code

* Formated code

* Supporting cairo v1 calldata structure

* Supporting cairo v1 calldata structure

* Support for some proxy contracts

* Fixed keccak calculation and added support for binary abi

* Removed debug information

* mix format

* fixed test
  • Loading branch information
ElFantasma authored Sep 15, 2023
1 parent 3f18fa8 commit 83bcfb5
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 125 deletions.
102 changes: 102 additions & 0 deletions lib/starknet_explorer/calldata.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
defmodule StarknetExplorer.Calldata do
def from_plain_calldata([array_len | rest], nil) do
{calls, [_calldata_length | calldata]} =
List.foldl(
Enum.to_list(1..felt_to_int(array_len)),
{[], rest},
fn _, {acc_current, acc_rest} ->
{new, new_rest} = get_call_header_v0(acc_rest)
{[new | acc_current], new_rest}
end
)

Enum.map(
Enum.reverse(calls),
fn call ->
%{call | :calldata => Enum.slice(calldata, call.data_offset, call.data_len)}
end
)
end

# we assume contract_class_version 0.1.0
def from_plain_calldata([array_len | rest], _contract_class_version) do
{calls, _} =
List.foldl(
Enum.to_list(1..felt_to_int(array_len)),
{[], rest},
fn _, {acc_current, acc_rest} ->
{new, new_rest} = get_call_header_v1(acc_rest)
{[new | acc_current], new_rest}
end
)

Enum.reverse(calls)
end

def get_call_header_v0([to, selector, data_offset, data_len | rest]) do
{%{
:address => to,
:selector => selector,
:data_offset => felt_to_int(data_offset),
:data_len => felt_to_int(data_len),
:calldata => []
}, rest}
end

def get_call_header_v1([to, selector, data_len | rest]) do
data_length = felt_to_int(data_len)
{calldata, rest} = Enum.split(rest, data_length)

{%{
:address => to,
:selector => selector,
:data_len => felt_to_int(data_len),
:calldata => calldata
}, rest}
end

def keccak(value) do
<<_::6, result::250>> = ExKeccak.hash_256(value)
("0x" <> Integer.to_string(result, 16)) |> String.downcase()
end

def as_fn_call(nil, _calldata) do
nil
end

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

def as_fn_inputs(inputs, calldata) do
{result, _} =
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}
end
)

Enum.reverse(result)
end

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

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

def get_value_for_type(_, [value | rest]) do
{value, rest}
end

def felt_to_int(<<"0x", hexa_value::binary>>) do
{value, _} = Integer.parse(hexa_value, 16)
value
end
end
126 changes: 124 additions & 2 deletions lib/starknet_explorer/data.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule StarknetExplorer.Data do
alias StarknetExplorer.{Rpc, Transaction, Block, TransactionReceipt, Events}
alias StarknetExplorer.{Rpc, Transaction, Block, TransactionReceipt, Calldata, Events}
alias StarknetExplorer.{Rpc, Transaction, Block, TransactionReceipt}
alias StarknetExplorerWeb.Utils

@implementation_selector "0x3a0ed1f62da1d3048614c2c1feb566f041c8467eb00fb8294776a9179dc1643"
@implementation_hash_selector "0x1d15dd5e6cac14c959221a0b45927b113a91fcfffa4c7bbab19b28d345467df"

@common_event_hash_to_name %{
"0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" => "Transfer",
"0x134692b230b9e1ffa39098904722134159652b09c5bc41d88d6698779d228ff" => "Approval",
Expand Down Expand Up @@ -159,6 +163,123 @@ defmodule StarknetExplorer.Data do
{:ok, tx}
end

def full_transaction(tx_hash, network) do
tx =
case Transaction.get_by_hash_with_receipt(tx_hash) do
nil ->
{:ok, tx} = Rpc.get_transaction(tx_hash, network)
{:ok, receipt} = Rpc.get_transaction_receipt(tx_hash, network)

block_id =
if receipt["block_number"],
do: %{"block_number" => receipt["block_number"]},
else: "latest"

{:ok, contract} =
Rpc.get_class_at(block_id, tx["sender_address"], network)

calldata =
Calldata.from_plain_calldata(tx["calldata"], contract["contract_class_version"])

input_data =
Enum.map(
calldata,
fn call ->
input = get_input_data(block_id, call.address, call.selector, network)
Map.put(call, :call, Calldata.as_fn_call(input, call.calldata))
end
)

tx
|> Transaction.from_rpc_tx()
|> Map.put(:receipt, receipt |> StarknetExplorerWeb.Utils.atomize_keys())
|> Map.put(:input_data, input_data)

tx ->
tx
end

{:ok, tx}
end

def get_input_data(block_id, address, selector, network) do
case Rpc.get_class_at(block_id, address, network) do
{:ok, class} ->
cond do
has_selector?(class, @implementation_selector) ->
{:ok, [implementation_address]} =
Rpc.call(block_id, address, @implementation_selector, network)

get_input_data(block_id, implementation_address, selector, network)

has_selector?(class, @implementation_hash_selector) ->
{:ok, [implementation_hash]} =
Rpc.call(block_id, address, @implementation_hash_selector, network)

get_input_data_for_hash(block_id, implementation_hash, selector, network)

true ->
find_by_selector(class, selector)
end

{:error, error} ->
error |> IO.inspect()
nil
end
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)

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

def find_by_selector(class, selector) do
abi =
case class["abi"] do
abi when is_binary(abi) ->
Jason.decode!(abi)

abi ->
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"] |> Calldata.keccak() == selector
end
)
end

# we assume contract_class_version 0.1.0
def find_by_selector_and_version(abi, _contract_class_version, selector) do
Enum.find(
abi,
fn elem ->
elem["name"] |> Calldata.keccak() == selector
end
)
end

def has_selector?(class, selector) do
Enum.any?(
class["entry_points_by_type"]["EXTERNAL"],
fn elem ->
elem["selector"] == selector
end
)
end

def get_block_events_paginated(block, pagination, network) do
# If the entries are empty, means that the events was not fetch yet.
with %Scrivener.Page{entries: []} <- Events.paginate_events(pagination, block.number, network) do
Expand All @@ -170,7 +291,8 @@ defmodule StarknetExplorer.Data do
end

def get_class_at(block_number, contract_address, network) do
{:ok, class_hash} = Rpc.get_class_at(block_number, contract_address, network)
{:ok, class_hash} =
Rpc.get_class_at(%{"block_number" => block_number}, contract_address, network)

class_hash
end
Expand Down
Loading

0 comments on commit 83bcfb5

Please sign in to comment.