Skip to content

Commit 57c0070

Browse files
authored
refactor: generalize aexn create contract mutation (#699)
1 parent b4908cf commit 57c0070

20 files changed

+504
-324
lines changed

lib/ae_mdw/aex9.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule AeMdw.Aex9 do
44
"""
55

66
alias AeMdw.Collection
7+
alias AeMdw.Contracts.AexnContract
78
alias AeMdw.Database
89
alias AeMdw.Db.Format
910
alias AeMdw.Db.Model
@@ -111,7 +112,7 @@ defmodule AeMdw.Aex9 do
111112
{:ok, balances_cursor() | nil, [aex9_balance()], balances_cursor() | nil}
112113
| {:error, Error.t()}
113114
def fetch_balances(contract_pk, pagination, cursor) do
114-
if AeMdw.Contract.is_aex9?(contract_pk) do
115+
if AexnContract.is_aex9?(contract_pk) do
115116
{amounts, _height_hash} = Db.aex9_balances!(contract_pk)
116117
accounts_pks = amounts |> Map.keys() |> Enum.sort()
117118
cursor = deserialize_balances_cursor(cursor)
@@ -388,7 +389,7 @@ defmodule AeMdw.Aex9 do
388389
end
389390

390391
defp validate_aex9(contract_pk) do
391-
if AeMdw.Contract.is_aex9?(contract_pk) do
392+
if AexnContract.is_aex9?(contract_pk) do
392393
:ok
393394
else
394395
{:error,

lib/ae_mdw/contract.ex

Lines changed: 6 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ defmodule AeMdw.Contract do
88
alias AeMdw.Node
99
alias AeMdw.Node.Db, as: DBN
1010
alias AeMdw.DryRun
11-
alias AeMdw.Log
12-
alias AeMdw.Validate
1311

1412
import :erlang, only: [tuple_to_list: 1]
1513

@@ -35,7 +33,6 @@ defmodule AeMdw.Contract do
3533

3634
@type call :: tuple()
3735

38-
@type aex9_meta_info() :: {String.t(), String.t(), integer()}
3936
# for balances or balance
4037
@type call_result :: map() | tuple()
4138
@type serialized_call :: map()
@@ -58,10 +55,6 @@ defmodule AeMdw.Contract do
5855
@typep tx :: Node.tx()
5956
@typep block_hash :: <<_::256>>
6057

61-
@aex9_transfer_event_hash <<34, 60, 57, 226, 157, 255, 100, 103, 254, 221, 160, 151, 88, 217,
62-
23, 129, 197, 55, 46, 9, 31, 248, 107, 58, 249, 227, 16, 227, 134,
63-
86, 43, 239>>
64-
6558
################################################################################
6659
@spec encode(atom(), binary()) :: String.t()
6760
defdelegate encode(type, val), to: :aeser_api_encoder
@@ -136,18 +129,19 @@ defmodule AeMdw.Contract do
136129
}
137130
end
138131

132+
@metadata_type {:variant, [tuple: [], tuple: [], tuple: [], tuple: []]}
133+
@option_string {:variant, [tuple: [], tuple: [:string]]}
134+
139135
@spec aex141_signatures() :: map()
140136
def aex141_signatures() do
141137
%{
142138
"aex141_extensions" => {[], {:list, :string}},
143139
"meta_info" =>
144-
{[], {:tuple, [:string, :string, {:variant, [tuple: [], tuple: [:string]]}, :atom]}},
145-
"metadata" =>
146-
{[:integer], {:variant, [tuple: [:string], tuple: [{:map, :string, :string}]]}},
147-
"mint" => {[:address, {:variant, [tuple: [], tuple: [:string]]}], :integer},
140+
{[],
141+
{:tuple, [:string, :string, {:variant, [tuple: [], tuple: [:string]]}, @metadata_type]}},
148142
"balance" => {[:address], {:variant, [tuple: [], tuple: [:integer]]}},
149143
"owner" => {[:integer], {:variant, [tuple: [], tuple: [:address]]}},
150-
"transfer" => {[:address, :address, :integer], {:tuple, []}},
144+
"transfer" => {[:address, :address, :integer, @option_string], {:tuple, []}},
151145
"approve" => {[:address, :integer, :boolean], {:tuple, []}},
152146
"approve_all" => {[:address, :boolean], {:tuple, []}},
153147
"get_approved" => {[:integer], {:variant, [tuple: [], tuple: [:address]]}},
@@ -172,83 +166,6 @@ defmodule AeMdw.Contract do
172166
def is_success_ct_call?(%{result: %{abort: _error}}), do: false
173167
def is_success_ct_call?(_result_ok), do: true
174168

175-
@spec is_non_stateful_aex9_function?(method_name()) :: boolean()
176-
def is_non_stateful_aex9_function?(<<method_name::binary>>) do
177-
method_name in [
178-
"aex9_extensions",
179-
"meta_info",
180-
"total_supply",
181-
"owner",
182-
"balance",
183-
"balances"
184-
]
185-
end
186-
187-
@spec get_aex9_transfer(DBN.pubkey(), String.t(), term()) ::
188-
{DBN.pubkey(), DBN.pubkey(), non_neg_integer()} | nil
189-
def get_aex9_transfer(from_pk, "transfer", [
190-
%{type: :address, value: to_account_id},
191-
%{type: :int, value: value}
192-
]),
193-
do: {from_pk, Validate.id!(to_account_id), value}
194-
195-
def get_aex9_transfer(_caller_pk, "transfer_allowance", [
196-
%{type: :address, value: from_account_id},
197-
%{type: :address, value: to_account_id},
198-
%{type: :int, value: value}
199-
]),
200-
do: {Validate.id!(from_account_id), Validate.id!(to_account_id), value}
201-
202-
def get_aex9_transfer(_caller_pk, _other_function, _other_args), do: nil
203-
204-
@spec is_aex9?(DBN.pubkey() | type_info()) :: boolean()
205-
def is_aex9?(pubkey) when is_binary(pubkey) do
206-
case get_info(pubkey) do
207-
{:ok, {type_info, _compiler_vsn, _source_hash}} -> is_aex9?(type_info)
208-
{:error, _reason} -> false
209-
end
210-
end
211-
212-
def is_aex9?({:fcode, functions, _hash_names, _code}) do
213-
AeMdw.Node.aex9_signatures()
214-
|> has_all_signatures?(functions)
215-
end
216-
217-
# AEVM
218-
def is_aex9?(_no_fcode), do: false
219-
220-
def is_aex141?({:fcode, functions, _hash_names, _code}) do
221-
AeMdw.Node.aex141_signatures()
222-
|> has_all_signatures?(functions)
223-
end
224-
225-
# AEVM
226-
def is_aex141?(_no_fcode), do: false
227-
228-
# value of :aec_hash.blake2b_256_hash("Transfer")
229-
@spec aex9_transfer_event_hash() :: event_hash()
230-
def aex9_transfer_event_hash(), do: @aex9_transfer_event_hash
231-
232-
@spec aex9_meta_info(DBN.pubkey()) :: {:ok, aex9_meta_info()} | :not_found
233-
def aex9_meta_info(contract_pk),
234-
do: aex9_meta_info(contract_pk, DBN.top_height_hash(false))
235-
236-
def aex9_meta_info(contract_pk, {type, height, hash}) do
237-
case call_contract(contract_pk, {type, height, hash}, "meta_info", []) do
238-
{:ok, {:tuple, {name, symbol, decimals}}} ->
239-
{:ok, {name, symbol, decimals}}
240-
241-
{:error, _call_error} ->
242-
Log.info(
243-
"aex9_meta_info not available for #{
244-
:aeser_api_encoder.encode(:contract_pubkey, contract_pk)
245-
}"
246-
)
247-
248-
:not_found
249-
end
250-
end
251-
252169
@spec call_contract(DBN.pubkey(), String.t(), list()) ::
253170
{:ok, call_result()} | {:error, any()} | :revert
254171
def call_contract(contract_pk, function_name, args),
@@ -427,12 +344,6 @@ defmodule AeMdw.Contract do
427344
#
428345
# Private functions
429346
#
430-
defp has_all_signatures?(aexn_signatures, functions) do
431-
Enum.all?(aexn_signatures, fn {hash, type} ->
432-
match?({_code, ^type, _body}, Map.get(functions, hash))
433-
end)
434-
end
435-
436347
# encoding and decoding vm data
437348
defp fate_val({:address, x}, f), do: f.({:address, encode(:account_pubkey, x)})
438349
defp fate_val({:oracle, x}, f), do: f.({:oracle, encode(:oracle_pubkey, x)})

lib/ae_mdw/contracts/aexn_contract.ex

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
defmodule AeMdw.Contracts.AexnContract do
2+
@moduledoc """
3+
AEX-N detection and common calls to interact with the contract.
4+
"""
5+
6+
import AeMdwWeb.Helpers.AexnHelper, only: [enc_ct: 1]
7+
8+
alias AeMdw.Contract
9+
alias AeMdw.Db.Model
10+
alias AeMdw.Node.Db, as: NodeDb
11+
alias AeMdw.Log
12+
13+
@typep pubkey :: NodeDb.pubkey()
14+
15+
@spec is_aex9?(pubkey() | Contract.type_info()) :: boolean()
16+
def is_aex9?(pubkey) when is_binary(pubkey) do
17+
case Contract.get_info(pubkey) do
18+
{:ok, {type_info, _compiler_vsn, _source_hash}} -> is_aex9?(type_info)
19+
{:error, _reason} -> false
20+
end
21+
end
22+
23+
def is_aex9?({:fcode, functions, _hash_names, _code}) do
24+
AeMdw.Node.aex9_signatures()
25+
|> has_all_signatures?(functions)
26+
end
27+
28+
def is_aex9?(_no_fcode), do: false
29+
30+
@spec is_aex141?(pubkey()) :: boolean()
31+
def is_aex141?(pubkey) when is_binary(pubkey) do
32+
with {:ok, {type_info, _compiler_vsn, _source_hash}} <- Contract.get_info(pubkey),
33+
true <- has_all_aex141_signatures?(type_info),
34+
{:ok, extensions} <- call_contract(pubkey, "extensions") do
35+
has_valid_aex141_extensions?(extensions, type_info)
36+
else
37+
_error_or_false ->
38+
false
39+
end
40+
end
41+
42+
def is_aex141?(_no_fcode), do: false
43+
44+
@spec call_meta_info(pubkey()) :: {:ok, Model.aexn_meta_info()} | :not_found
45+
def call_meta_info(contract_pk) do
46+
with {:ok, {:tuple, meta_info_tuple}} <- call_contract(contract_pk, "meta_info", []) do
47+
{:ok, meta_info_tuple}
48+
end
49+
end
50+
51+
#
52+
# Private functions
53+
#
54+
defp call_contract(contract_pk, method, args \\ []) do
55+
top_hash = NodeDb.top_height_hash(false)
56+
57+
case Contract.call_contract(contract_pk, top_hash, method, args) do
58+
{:ok, return} ->
59+
{:ok, return}
60+
61+
{:error, _call_error} ->
62+
Log.warn("#{method} call error for #{enc_ct(contract_pk)}")
63+
:not_found
64+
end
65+
end
66+
67+
defp has_all_signatures?(aexn_signatures, functions) do
68+
Enum.all?(aexn_signatures, fn {hash, type} ->
69+
match?({_code, ^type, _body}, Map.get(functions, hash))
70+
end)
71+
end
72+
73+
defp has_all_aex141_signatures?({:fcode, functions, _hash_names, _code}) do
74+
valid_base_signatures? =
75+
AeMdw.Node.aex141_signatures()
76+
|> has_all_signatures?(functions)
77+
78+
valid_base_signatures? and valid_aex141_metadata?(functions)
79+
end
80+
81+
defp has_all_aex141_signatures?(_no_fcode), do: false
82+
83+
@option_string {:variant, [tuple: [], tuple: [:string]]}
84+
@option_metadata_map {:variant, [tuple: [], tuple: [{:map, :string, :string}]]}
85+
@metadata_hash <<99, 148, 233, 122>>
86+
@mint_hash <<207, 221, 154, 162>>
87+
@burn_hash <<177, 239, 193, 123>>
88+
@swap_hash <<17, 0, 79, 166>>
89+
@check_swap_hash <<214, 57, 13, 126>>
90+
@swapped_hash <<29, 236, 102, 255>>
91+
92+
defp valid_aex141_metadata?(functions) do
93+
case Map.get(functions, @metadata_hash) do
94+
nil ->
95+
false
96+
97+
{_code, type, _body} ->
98+
type == {[:integer], @option_string} or type == {[:integer], @option_metadata_map}
99+
end
100+
end
101+
102+
defp has_valid_aex141_extensions?(extensions, {:fcode, functions, _hash_names, _code}) do
103+
Enum.all?(extensions, &valid_aex141_extension?(&1, functions))
104+
end
105+
106+
defp valid_aex141_extension?("mintable", functions) do
107+
case Map.get(functions, @mint_hash) do
108+
nil ->
109+
false
110+
111+
{_code, type, _body} ->
112+
type == {[:address, @option_string], :integer} or
113+
type == {[:address, @option_metadata_map], :integer}
114+
end
115+
end
116+
117+
defp valid_aex141_extension?("burnable", functions) do
118+
match?({_code, {[:integer], {:tuple, []}}, _body}, functions[@burn_hash])
119+
end
120+
121+
defp valid_aex141_extension?("swappable", functions) do
122+
match?({_code, {[], {:tuple, []}}, _body}, functions[@swap_hash]) and
123+
match?({_code, {[:address], :integer}, _body}, functions[@check_swap_hash]) and
124+
match?({_code, {[], {:map, :address, :string}}, _body}, functions[@swapped_hash])
125+
end
126+
end

lib/ae_mdw/db/contract.ex

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule AeMdw.Db.Contract do
44
"""
55
alias AeMdw.Collection
66
alias AeMdw.Contract
7+
alias AeMdw.Contracts.AexnContract
78
alias AeMdw.Database
89
alias AeMdw.Db.Model
910
alias AeMdw.Db.Origin
@@ -29,16 +30,25 @@ defmodule AeMdw.Db.Contract do
2930
@typep block_index :: AeMdw.Blocks.block_index()
3031
@typep txi :: AeMdw.Txs.txi()
3132

32-
@spec aex9_creation_write(state(), Contract.aex9_meta_info(), pubkey(), integer()) :: state()
33-
def aex9_creation_write(state, {name, symbol, decimals}, contract_pk, txi) do
34-
m_contract_name = Model.aexn_contract_name(index: {:aex9, name, contract_pk})
35-
m_contract_sym = Model.aexn_contract_symbol(index: {:aex9, symbol, contract_pk})
33+
@spec aexn_creation_write(
34+
state(),
35+
Model.aexn_type(),
36+
Model.aexn_meta_info(),
37+
pubkey(),
38+
integer()
39+
) :: state()
40+
def aexn_creation_write(state, aexn_type, aexn_meta_info, contract_pk, txi) do
41+
name = elem(aexn_meta_info, 0)
42+
symbol = elem(aexn_meta_info, 1)
43+
44+
m_contract_name = Model.aexn_contract_name(index: {aexn_type, name, contract_pk})
45+
m_contract_sym = Model.aexn_contract_symbol(index: {aexn_type, symbol, contract_pk})
3646

3747
m_contract_pk =
3848
Model.aexn_contract(
39-
index: {:aex9, contract_pk},
49+
index: {aexn_type, contract_pk},
4050
txi: txi,
41-
meta_info: {name, symbol, decimals}
51+
meta_info: aexn_meta_info
4252
)
4353

4454
state
@@ -236,11 +246,11 @@ defmodule AeMdw.Db.Contract do
236246

237247
@spec which_aexn_contract_pubkey(pubkey(), pubkey()) :: pubkey() | nil
238248
def which_aexn_contract_pubkey(contract_pk, addr) do
239-
if Contract.is_aex9?(contract_pk) do
249+
if AexnContract.is_aex9?(contract_pk) do
240250
contract_pk
241251
else
242252
# remotely called contract is aex9?
243-
if addr != contract_pk and Contract.is_aex9?(addr), do: addr
253+
if addr != contract_pk and AexnContract.is_aex9?(addr), do: addr
244254
end
245255
end
246256

@@ -346,11 +356,11 @@ defmodule AeMdw.Db.Contract do
346356

347357
@spec update_aex9_state(State.t(), pubkey(), block_index(), txi()) :: State.t()
348358
def update_aex9_state(state, contract_pk, {kbi, mbi} = block_index, txi) do
349-
if Contract.is_aex9?(contract_pk) do
359+
if AexnContract.is_aex9?(contract_pk) do
350360
with false <- State.exists?(state, Model.AexnContract, {:aex9, contract_pk}),
351-
{:ok, aex9_meta_info} <- Contract.aex9_meta_info(contract_pk) do
361+
{:ok, aex9_meta_info} <- AexnContract.call_meta_info(contract_pk) do
352362
AsyncTasks.Producer.enqueue(:derive_aex9_presence, [contract_pk, kbi, mbi, txi])
353-
aex9_creation_write(state, aex9_meta_info, contract_pk, txi)
363+
aexn_creation_write(state, :aex9, aex9_meta_info, contract_pk, txi)
354364
else
355365
true ->
356366
AsyncTasks.Producer.enqueue(:update_aex9_state, [contract_pk], [block_index, txi])

lib/ae_mdw/db/model.ex

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,16 @@ defmodule AeMdw.Db.Model do
302302
# index: {type, pubkey} where type = :aex9, :aex141, ...
303303
# txi: txi
304304
# meta_info: {name, symbol, decimals} | {name, symbol, base_url, metadata_type}
305-
@type aexn_meta_info ::
306-
{String.t(), String.t(), non_neg_integer()}
307-
| {String.t(), String.t(), String.t(), atom()}
305+
@type aexn_type :: :aex9 | :aex141
306+
@type aexn_name :: String.t()
307+
@type aexn_symbol :: String.t()
308+
@type aex9_meta_info :: {aexn_name(), aexn_symbol(), non_neg_integer()}
309+
@type aex141_meta_info :: {aexn_name(), aexn_symbol(), String.t(), atom()}
310+
@type aexn_meta_info :: aex9_meta_info() | aex141_meta_info()
311+
308312
@type aexn_contract ::
309313
record(:aexn_contract,
310-
index: {:aex9 | :aex141, Db.pubkey()},
314+
index: {aexn_type(), Db.pubkey()},
311315
txi: Txs.txi(),
312316
meta_info: aexn_meta_info()
313317
)

0 commit comments

Comments
 (0)