Skip to content

Commit

Permalink
chore: move aexn tokens rendering to contract modules (#1807)
Browse files Browse the repository at this point in the history
  • Loading branch information
sborrazas committed Jun 12, 2024
1 parent 0ce2020 commit 6d676cf
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 286 deletions.
79 changes: 43 additions & 36 deletions lib/ae_mdw/aex141.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule AeMdw.Aex141 do
alias AeMdw.Stats
alias AeMdw.Txs
alias AeMdw.Util
alias AeMdw.Validate

import AeMdw.Util.Encoding

Expand Down Expand Up @@ -72,15 +73,16 @@ defmodule AeMdw.Aex141 do
end
end

@spec fetch_owned_nfts(State.t(), pubkey(), pubkey(), cursor(), pagination()) ::
@spec fetch_owned_tokens(State.t(), binary(), cursor(), pagination(), map()) ::
{:ok, {page_cursor(), [nft()], page_cursor()}} | {:error, Error.t()}
def fetch_owned_nfts(state, account_pk, contract_pk, cursor, pagination) do
with {:ok, cursor_key} <- deserialize_cursor(@ownership_table, cursor),
cursor_key <- update_cursor(cursor_key, account_pk, contract_pk) do
def fetch_owned_tokens(state, account_id, cursor, pagination, params) do
with {:ok, account_pk} <- Validate.id(account_id),
{:ok, filters} <- Util.convert_params(params, &convert_owned_tokens_param/1),
{:ok, cursor} <- deserialize_ownership_cursor(account_pk, cursor) do
paginated_nfts =
state
|> build_streamer(@ownership_table, cursor_key, account_pk, contract_pk)
|> Collection.paginate(pagination, &render_owned_nft/1, &serialize_cursor/1)
|> build_owned_tokens_streamer(account_pk, cursor, filters)
|> Collection.paginate(pagination, &render_owned_nft/1, &serialize_ownership_cursor/1)

{:ok, paginated_nfts}
end
Expand Down Expand Up @@ -166,13 +168,20 @@ defmodule AeMdw.Aex141 do
#
# Private function
#
defp build_streamer(state, Model.NftOwnership, cursor_key, account_pk, contract_pk) do
defp build_owned_tokens_streamer(state, account_pk, cursor, %{contract: contract_pk}) do
key_boundary =
{{account_pk, contract_pk || <<>>, 0},
{account_pk, contract_pk || Util.max_256bit_bin(), Util.max_int()}}
{{account_pk, contract_pk, Util.min_int()}, {account_pk, contract_pk, Util.max_int()}}

fn direction ->
Collection.stream(state, Model.NftOwnership, direction, key_boundary, cursor_key)
Collection.stream(state, @ownership_table, direction, key_boundary, cursor)
end
end

defp build_owned_tokens_streamer(state, account_pk, cursor, _params) do
key_boundary = {{account_pk, <<>>, 0}, {account_pk, Util.max_256bit_bin(), Util.max_int()}}

fn direction ->
Collection.stream(state, @ownership_table, direction, key_boundary, cursor)
end
end

Expand All @@ -196,16 +205,6 @@ defmodule AeMdw.Aex141 do

defp deserialize_cursor(_table, nil), do: {:ok, nil}

defp deserialize_cursor(Model.NftOwnership, cursor_bin64) do
with {:ok, cursor_bin} <- Base.decode64(cursor_bin64),
{<<_pk1::256>>, <<_pk2::256>>, token_id} = cursor when is_integer(token_id) <-
:erlang.binary_to_term(cursor_bin) do
{:ok, cursor}
else
_invalid -> {:error, ErrInput.Cursor.exception(value: cursor_bin64)}
end
end

defp deserialize_cursor(Model.NftTemplate, cursor_bin64) do
with {:ok, cursor_bin} <- Base.decode64(cursor_bin64),
{<<_pk::256>>, template_id} = cursor when is_integer(template_id) <-
Expand Down Expand Up @@ -237,6 +236,30 @@ defmodule AeMdw.Aex141 do
end
end

defp serialize_ownership_cursor({_account_pk, contract_pk, token_id}),
do: serialize_cursor({contract_pk, token_id})

defp deserialize_ownership_cursor(_account_pk, nil), do: {:ok, nil}

defp deserialize_ownership_cursor(account_pk, cursor_bin64) do
with {:ok, cursor_bin} <- Base.decode64(cursor_bin64),
{<<_pk::256>> = contract_pk, token_id} when is_integer(token_id) <-
:erlang.binary_to_term(cursor_bin) do
{:ok, {account_pk, contract_pk, token_id}}
else
_invalid -> {:error, ErrInput.Cursor.exception(value: cursor_bin64)}
end
end

defp convert_owned_tokens_param({"contract", contract_id}) do
with {:ok, contract_pk} <- Validate.id(contract_id) do
{:ok, {:contract, contract_pk}}
end
end

defp convert_owned_tokens_param(other_param),
do: {:error, ErrInput.Query.exception(value: other_param)}

defp call_contract(state, contract_pk, entrypoint, args) do
with true <- State.exists?(state, Model.AexnContract, {:aex141, contract_pk}),
{:ok, {:variant, [0, 1], 1, {result}}} <-
Expand Down Expand Up @@ -267,22 +290,6 @@ defmodule AeMdw.Aex141 do
end
end

defp update_cursor(nil, _account_pk, _contract_pk), do: nil

defp update_cursor({account_pk, _contract_pk, _token} = cursor_key, account_pk, nil),
do: cursor_key

defp update_cursor({account_pk, contract_pk, _token} = cursor_key, account_pk, contract_pk),
do: cursor_key

defp update_cursor({account_pk, _contract_pk1, _token}, account_pk, contract_pk2),
do: {account_pk, contract_pk2, 0}

defp update_cursor({_account_pk1, _contract_pk, _token}, account_pk2, contract_pk),
do: {account_pk2, contract_pk || <<>>, 0}

defp update_cursor(cursor_key, _account_pk, _contract_pk), do: cursor_key

defp render_owned_nft({owner_pk, contract_pk, token_id}) do
%{
contract_id: encode_contract(contract_pk),
Expand Down
128 changes: 115 additions & 13 deletions lib/ae_mdw/aexn_tokens.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ defmodule AeMdw.AexnTokens do
Context module for AEX-N tokens.
"""

alias :aeser_api_encoder, as: Enc
alias AeMdw.Aex141
alias AeMdw.Aex9
alias AeMdw.Collection
alias AeMdw.Db.Model
alias AeMdw.Db.State
alias AeMdw.Error
alias AeMdw.Error.Input, as: ErrInput
alias AeMdw.Node.Db
alias AeMdw.Stats
alias AeMdw.Txs
alias AeMdw.Util
alias AeMdw.Validate

Expand All @@ -24,6 +28,7 @@ defmodule AeMdw.AexnTokens do
@typep pagination :: Collection.direction_limit()
@typep order_by() :: :name | :symbol | :creation
@typep query :: %{binary() => binary()}
@typep aexn_contract() :: map()

@max_sort_field_length 100

Expand All @@ -37,25 +42,37 @@ defmodule AeMdw.AexnTokens do
creation: @aexn_creation_table
}

@spec fetch_contract(State.t(), {aexn_type(), Db.pubkey()}) ::
{:ok, Model.aexn_contract()} | {:error, Error.t()}
def fetch_contract(state, {aexn_type, contract_pk}) do
with {:ok, m_aexn} <- State.get(state, Model.AexnContract, {aexn_type, contract_pk}),
@spec fetch_contract(State.t(), aexn_type(), binary(), boolean()) ::
{:ok, aexn_contract()} | {:error, Error.t()}
def fetch_contract(state, aexn_type, contract_id, v3?) do
with {:ok, contract_pk} <- Validate.id(contract_id),
{:ok, aexn_contract} <- State.get(state, Model.AexnContract, {aexn_type, contract_pk}),
{:invalid, false} <-
{:invalid, State.exists?(state, Model.AexnInvalidContract, {aexn_type, contract_pk})} do
{:ok, m_aexn}
{:ok, render_contract(state, aexn_contract, v3?)}
else
:not_found ->
{:error, ErrInput.NotFound.exception(value: encode_contract(contract_pk))}

{:invalid, true} ->
{:error, ErrInput.AexnContractInvalid.exception(value: encode_contract(contract_pk))}
{:error, ErrInput.AexnContractInvalid.exception(value: contract_id)}

{:error, reason} ->
{:error, reason}

:not_found ->
{:error, ErrInput.NotFound.exception(value: contract_id)}
end
end

@spec fetch_contracts(State.t(), pagination(), aexn_type(), query(), order_by(), cursor() | nil) ::
@spec fetch_contracts(
State.t(),
pagination(),
aexn_type(),
query(),
order_by(),
cursor() | nil,
boolean()
) ::
{:ok, {cursor() | nil, [Model.aexn_contract()], cursor() | nil}} | {:error, Error.t()}
def fetch_contracts(state, pagination, aexn_type, query, order_by, cursor) do
def fetch_contracts(state, pagination, aexn_type, query, order_by, cursor, v3?) do
with {:ok, cursor} <- deserialize_aexn_cursor(cursor),
{:ok, params} <- validate_params(query),
{:ok, filters} <- Util.convert_params(params, &convert_param/1) do
Expand All @@ -64,7 +81,11 @@ defmodule AeMdw.AexnTokens do
paginated_aexn_contracts =
filters
|> build_tokens_streamer(state, aexn_type, sorting_table, cursor)
|> Collection.paginate(pagination, & &1, &serialize_aexn_cursor(order_by, &1))
|> Collection.paginate(
pagination,
&render_contract(state, &1, v3?),
&serialize_aexn_cursor(order_by, &1)
)

{:ok, paginated_aexn_contracts}
end
Expand Down Expand Up @@ -179,4 +200,85 @@ defmodule AeMdw.AexnTokens do
end

defp is_valid_cursor_term?(_other_term), do: false

defp render_contract(
state,
Model.aexn_contract(
index: {:aex9, contract_pk} = index,
txi_idx: {txi, _idx},
meta_info: {name, symbol, decimals},
extensions: extensions
),
v3?
) do
initial_supply =
case State.get(state, Model.Aex9InitialSupply, contract_pk) do
{:ok, Model.aex9_initial_supply(amount: amount)} -> amount
:not_found -> 0
end

event_supply =
case State.get(state, Model.Aex9ContractBalance, contract_pk) do
{:ok, Model.aex9_contract_balance(amount: amount)} -> amount
:not_found -> 0
end

num_holders = Aex9.fetch_holders_count(state, contract_pk)

response = %{
name: name,
symbol: symbol,
decimals: decimals,
contract_id: encode_contract(contract_pk),
extensions: extensions,
initial_supply: initial_supply,
event_supply: event_supply,
holders: num_holders,
invalid: State.exists?(state, Model.AexnInvalidContract, index),
logs_count: Stats.fetch_aex9_logs_count(state, contract_pk)
}

case v3? do
true ->
Map.put(response, :contract_tx_hash, Enc.encode(:tx_hash, Txs.txi_to_hash(state, txi)))

false ->
Map.put(response, :contract_txi, txi)
end
end

defp render_contract(
state,
Model.aexn_contract(
index: {:aex141, contract_pk} = index,
txi_idx: {txi, _idx},
meta_info: {name, symbol, base_url, metadata_type},
extensions: extensions
),
v3?
) do
%{
name: name,
symbol: symbol,
base_url: base_url,
contract_txi: txi,
contract_id: encode_contract(contract_pk),
metadata_type: metadata_type,
extensions: extensions,
limits: Aex141.fetch_limits(state, contract_pk, v3?),
invalid: State.exists?(state, Model.AexnInvalidContract, index)
}
|> maybe_put_contract_tx_hash(state, txi, v3?)
|> Map.merge(Stats.fetch_nft_stats(state, contract_pk))
end

defp maybe_put_contract_tx_hash(data, state, txi, v3?) do
if v3? do
data
|> Map.put(:contract_tx_hash, Enc.encode(:tx_hash, Txs.txi_to_hash(state, txi)))
|> Map.delete(:contract_txi)
else
data
end
end
end
20 changes: 6 additions & 14 deletions lib/ae_mdw_web/controllers/aex141_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,25 +108,17 @@ defmodule AeMdwWeb.Aex141Controller do
end

@spec owned_nfts(Conn.t(), map()) :: Conn.t() | {:error, ErrInput.t()}
def owned_nfts(%Conn{assigns: assigns} = conn, %{"account_id" => account_id} = params) do
def owned_nfts(%Conn{assigns: assigns} = conn, %{"account_id" => account_id}) do
%{
state: state,
pagination: pagination,
cursor: cursor
cursor: cursor,
query: query
} = assigns

with {:ok, account_pk} <- Validate.id(account_id),
{:ok, contract_pk} <- validate_optional_pubkey(params, "contract"),
{:ok, {prev_cursor, nfts, next_cursor}} <-
Aex141.fetch_owned_nfts(state, account_pk, contract_pk, cursor, pagination) do
Util.render(conn, prev_cursor, nfts, next_cursor)
end
end

defp validate_optional_pubkey(params, param_name) do
case Map.fetch(params, param_name) do
{:ok, id} -> Validate.id(id)
:error -> {:ok, nil}
with {:ok, paginated_tokens} <-
Aex141.fetch_owned_tokens(state, account_id, cursor, pagination, query) do
Util.render(conn, paginated_tokens)
end
end
end
12 changes: 6 additions & 6 deletions lib/ae_mdw_web/controllers/aex9_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -196,26 +196,26 @@ defmodule AeMdwWeb.Aex9Controller do
#
defp by_contract_reply(%Conn{assigns: %{state: state}} = conn, contract_id) do
with {:ok, contract_pk} <- Validate.id(contract_id, [:contract_pubkey]),
{:ok, m_aex9} <- AexnTokens.fetch_contract(state, {:aex9, contract_pk}) do
format_json(conn, %{data: render_contract(state, m_aex9, false)})
{:ok, contract} <- AexnTokens.fetch_contract(state, :aex9, contract_pk, false) do
format_json(conn, %{data: contract})
end
end

defp by_names_reply(%Conn{assigns: %{state: state}} = conn, params) do
pagination = {:forward, false, 32_000, false}

with {:ok, {_prev_cursor, aex9_tokens, _next_cursor}} <-
AexnTokens.fetch_contracts(state, pagination, :aex9, params, :name, nil) do
format_json(conn, render_contracts(state, aex9_tokens, false))
AexnTokens.fetch_contracts(state, pagination, :aex9, params, :name, nil, false) do
format_json(conn, aex9_tokens)
end
end

defp by_symbols_reply(%Conn{assigns: %{state: state}} = conn, params) do
pagination = {:forward, false, 32_000, false}

with {:ok, {_prev_cursor, aex9_tokens, _next_cursor}} <-
AexnTokens.fetch_contracts(state, pagination, :aex9, params, :symbol, nil) do
format_json(conn, render_contracts(state, aex9_tokens, false))
AexnTokens.fetch_contracts(state, pagination, :aex9, params, :symbol, nil, false) do
format_json(conn, aex9_tokens)
end
end

Expand Down
15 changes: 7 additions & 8 deletions lib/ae_mdw_web/controllers/aexn_token_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ defmodule AeMdwWeb.AexnTokenController do
alias AeMdwWeb.Util
alias Plug.Conn

import AeMdwWeb.AexnView

use AeMdwWeb, :controller

require Model
Expand Down Expand Up @@ -174,17 +172,18 @@ defmodule AeMdwWeb.AexnTokenController do
aexn_type,
query,
order_by,
cursor
cursor,
v3?
) do
Util.render(conn, paginated_contracts, &render_contract(state, &1, v3?))
Util.render(conn, paginated_contracts)
end
end

defp aexn_contract(%Conn{assigns: %{state: state} = assigns} = conn, contract_id, aexn_type) do
with {:ok, contract_pk} <- Validate.id(contract_id, [:contract_pubkey]),
{:ok, m_aexn} <- AexnTokens.fetch_contract(state, {aexn_type, contract_pk}) do
v3? = Map.get(assigns, :v3?, true)
format_json(conn, render_contract(state, m_aexn, v3?))
v3? = Map.get(assigns, :v3?, true)

with {:ok, aexn_contract} <- AexnTokens.fetch_contract(state, aexn_type, contract_id, v3?) do
format_json(conn, aexn_contract)
end
end

Expand Down
Loading

0 comments on commit 6d676cf

Please sign in to comment.