Compare commits

...

5 Commits

Author SHA1 Message Date
SpiveeWorks
f54a33a293 Fix type substitution into variants and records
Variants were working by accident, since
{variant, [{"VariantName", [Element]}]} had a similar enough form to
the opaque types that would come from something like
`type1(type2(int))`, but records were not working, since they have a
different form. Now both are handled explicitly so that only the
intended forms of each are handled.
2025-01-24 19:14:16 +11:00
SpiveeWorks
1d71c16d6a Also prepare AACI for namespace types 2025-01-24 19:14:16 +11:00
SpiveeWorks
28dba962af Even more unit tests
Trying to test all the basic types that coerce covers, and a couple more
type parameter and nested cases.
2025-01-24 19:14:16 +11:00
SpiveeWorks
6e335067f9 Add unit tests for some simple coercions 2025-01-24 19:14:15 +11:00
b69ababf0f Remove oracles, update deps (except sophia) 2025-01-23 23:51:18 +09:00
2 changed files with 220 additions and 35 deletions

View File

@ -45,7 +45,7 @@
acc/1, acc_at_height/2, acc_at_block_id/2,
acc_pending_txs/1,
next_nonce/1,
dry_run/1, dry_run/2, dry_run/3,
dry_run/1, dry_run/2, dry_run/3, dry_run_map/1,
tx/1, tx_info/1,
post_tx/1,
contract/1, contract_code/1,
@ -76,6 +76,7 @@
-export_type([chain_node/0, network_id/0, chain_error/0]).
-include_lib("eunit/include/eunit.hrl").
-type chain_node() :: {inet:ip_address(), inet:port_number()}.
-type network_id() :: string().
@ -643,6 +644,12 @@ dry_run(TX, Accounts, KBHash) ->
JSON = zj:binary_encode(DryData),
request("/v3/dry_run", JSON).
dry_run_map(Map) ->
JSON = zj:binary_encode(Map),
request("/v3/dry_run", JSON).
-spec decode_bytearray_fate(EncodedStr) -> {ok, Result} | {error, Reason}
when EncodedStr :: binary() | string(),
Result :: none | term(),
@ -762,18 +769,6 @@ contract_code(ID) ->
contract_poi(ID) ->
request(["/v3/contracts/", ID, "/poi"]).
% TODO
%oracle(ID) ->
% request(["/v3/oracles/", ID]).
% TODO
%oracle_queries(ID) ->
% request(["/v3/oracles/", ID, "/queries"]).
% TODO
%oracle_queries_by_id(OracleID, QueryID) ->
% request(["/v3/oracles/", OracleID, "/queries/", QueryID]).
-spec name(Name) -> {ok, Info} | {error, Reason}
when Name :: string(), % _ ++ ".chain"
@ -797,7 +792,7 @@ name(Name) ->
when Pubkey :: peer_pubkey(),
Reason :: term(). % FIXME
%% @doc
%% Returns the given node's public key, assuming there an AE node is reachable at
%% Returns the given node's public key, assuming a node is reachable at
%% the given address.
peer_pubkey() ->
@ -1014,6 +1009,18 @@ contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) ->
end.
-spec contract_create_built(CreatorID, Compiled, InitArgs) -> Result
when CreatorID :: unicode:chardata(),
Compiled :: map(),
InitArgs :: [string()],
Result :: {ok, CreateTX} | {error, Reason},
CreateTX :: binary(),
Reason :: file:posix() | term().
%% @doc
%% This function takes the compiler output (instead of starting from source),
%% and returns the unsigned create contract call data with default values.
%% For more control over exactly what those values are, use create_contract/8.
contract_create_built(CreatorID, Compiled, InitArgs) ->
case next_nonce(CreatorID) of
{ok, Nonce} ->
@ -1385,10 +1392,7 @@ prepare_contract(File) ->
end.
prepare_aaci(ACI) ->
% NOTE this will also pick up the main contract; as a result the main
% contract extraction later on shouldn't bother with typedefs.
Contracts = [ContractDef || #{contract := ContractDef} <- ACI],
Types = simplify_contract_types(Contracts, #{}),
Types = lists:foldl(fun prepare_namespace_types/2, #{}, ACI),
[{NameBin, SpecDefs}] =
[{N, F}
@ -1399,22 +1403,29 @@ prepare_aaci(ACI) ->
Specs = simplify_specs(SpecDefs, #{}, Types),
{aaci, Name, Specs, Types}.
simplify_contract_types([], Types) ->
Types;
simplify_contract_types([Next | Rest], Types) ->
TypeDefs = maps:get(typedefs, Next),
NameBin = maps:get(name, Next),
prepare_namespace_types(#{namespace := NS}, Types) ->
prepare_namespace_types2(NS, false, Types);
prepare_namespace_types(#{contract := NS}, Types) ->
prepare_namespace_types2(NS, true, Types).
prepare_namespace_types2(NS, IsContract, Types) ->
TypeDefs = maps:get(typedefs, NS),
NameBin = maps:get(name, NS),
Name = binary_to_list(NameBin),
Types2 = maps:put(Name, {[], contract}, Types),
Types3 = case maps:find(state, Next) of
Types2 = case IsContract of
true ->
maps:put(Name, {[], contract}, Types);
false ->
Types
end,
Types3 = case maps:find(state, NS) of
{ok, StateDefACI} ->
StateDefOpaque = opaque_type([], StateDefACI),
maps:put(Name ++ ".state", {[], StateDefOpaque}, Types2);
error ->
Types2
end,
Types4 = simplify_typedefs(TypeDefs, Types3, Name ++ "."),
simplify_contract_types(Rest, Types4).
simplify_typedefs(TypeDefs, Types3, Name ++ ".").
simplify_typedefs([], Types, _NamePrefix) ->
Types;
@ -1638,12 +1649,39 @@ substitute_opaque_type(Bindings, {var, VarName}) ->
false -> {error, invalid_aci};
{_, TypeArg} -> {ok, TypeArg}
end;
substitute_opaque_type(Bindings, {variant, Args}) ->
case substitute_variant_types(Bindings, Args, []) of
{ok, Result} -> {ok, {variant, Result}};
Error -> Error
end;
substitute_opaque_type(Bindings, {record, Args}) ->
case substitute_record_types(Bindings, Args, []) of
{ok, Result} -> {ok, {record, Result}};
Error -> Error
end;
substitute_opaque_type(Bindings, {Connective, Args}) ->
case substitute_opaque_types(Bindings, Args, []) of
{ok, Result} -> {ok, {Connective, Result}};
Error -> Error
end;
substitute_opaque_type(_Bindings, Type) -> {ok, Type}.
substitute_opaque_type(_Bindings, Type) ->
{ok, Type}.
substitute_variant_types(Bindings, [{VariantName, Elements} | Rest], Acc) ->
case substitute_opaque_types(Bindings, Elements, []) of
{ok, Result} -> substitute_variant_types(Bindings, Rest, [{VariantName, Result} | Acc]);
Error -> Error
end;
substitute_variant_types(_Bindings, [], Acc) ->
{ok, lists:reverse(Acc)}.
substitute_record_types(Bindings, [{ElementName, Type} | Rest], Acc) ->
case substitute_opaque_type(Bindings, Type) of
{ok, Result} -> substitute_record_types(Bindings, Rest, [{ElementName, Result} | Acc]);
Error -> Error
end;
substitute_record_types(_Bindings, [], Acc) ->
{ok, lists:reverse(Acc)}.
substitute_opaque_types(Bindings, [Next | Rest], Acc) ->
case substitute_opaque_type(Bindings, Next) of
@ -2134,3 +2172,151 @@ eu(N, Size) ->
% /v3/debug/check-tx/pool/{hash}
% /v3/debug/token-supply/height/{height}
% /v3/debug/crash
%%% Simple coerce/3 tests
try_coerce(Type, Sophia, Fate) ->
FateActual = coerce(Type, Sophia, to_fate),
SophiaActual = coerce(Type, Fate, from_fate),
case {ok, Fate} == FateActual of
true ->
ok;
false ->
erlang:error({to_fate_failed, Fate, FateActual})
end,
case {ok, Sophia} == SophiaActual of
true ->
ok;
false ->
erlang:error({from_fate_failed, Sophia, SophiaActual})
end,
ok.
coerce_int_test() ->
{ok, Type} = flatten_opaque_type(integer, #{}),
try_coerce(Type, 123, 123).
coerce_address_test() ->
{ok, Type} = flatten_opaque_type(address, #{}),
try_coerce(Type,
"ak_2FTnrGfV8qsfHpaSEHpBrziioCpwwzLqSevHqfxQY3PaAAdARx",
{address, <<164,136,155,90,124,22,40,206,255,76,213,56,238,123,
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
210,39,214>>}).
coerce_contract_test() ->
{ok, Type} = flatten_opaque_type(contract, #{}),
try_coerce(Type,
"ct_2FTnrGfV8qsfHpaSEHpBrziioCpwwzLqSevHqfxQY3PaAAdARx",
{contract, <<164,136,155,90,124,22,40,206,255,76,213,56,238,123,
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
210,39,214>>}).
coerce_bool_test() ->
{ok, Type} = flatten_opaque_type(boolean, #{}),
try_coerce(Type, true, true),
try_coerce(Type, false, false).
coerce_string_test() ->
{ok, Type} = flatten_opaque_type(string, #{}),
try_coerce(Type, "hello world", <<"hello world">>).
coerce_list_test() ->
{ok, Type} = flatten_opaque_type({list, [string]}, #{}),
try_coerce(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]).
coerce_map_test() ->
{ok, Type} = flatten_opaque_type({map, [string, {list, [integer]}]}, #{}),
try_coerce(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}).
coerce_tuple_test() ->
{ok, Type} = flatten_opaque_type({tuple, [integer, string]}, #{}),
try_coerce(Type, {123, "456"}, {tuple, {123, <<"456">>}}).
coerce_variant_test() ->
{ok, Type} = flatten_opaque_type({variant, [{"A", [integer]},
{"B", [integer, integer]}]},
#{}),
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}).
coerce_record_test() ->
{ok, Type} = flatten_opaque_type({record, [{"a", integer}, {"b", integer}]}, #{}),
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
%%% Complex AACI paramter and namespace tests
aaci_from_string(String) ->
case aeso_compiler:from_string(String, [{aci, json}]) of
{ok, #{aci := ACI}} -> {ok, prepare_aaci(ACI)};
Error -> Error
end.
namespace_coerce_test() ->
Contract = "
namespace N =
record pair = { a : int, b : int }
contract C =
entrypoint f(): N.pair = { a = 1, b = 2 }
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
record_substitution_test() ->
Contract = "
contract C =
record pair('t) = { a : 't, b : 't }
entrypoint f(): pair(int) = { a = 1, b = 2 }
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
tuple_substitution_test() ->
Contract = "
contract C =
type triple('t1, 't2) = int * 't1 * 't2
entrypoint f(): triple(int, string) = (1, 2, \"hello\")
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
try_coerce(Output, {1, 2, "hello"}, {tuple, {1, 2, <<"hello">>}}).
variant_substitution_test() ->
Contract = "
contract C =
datatype adt('a, 'b) = Left('a, 'b) | Right('b, int)
entrypoint f(): adt(string, int) = Left(\"hi\", 1)
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
try_coerce(Output, {"Left", "hi", 1}, {variant, [2, 2], 0, {<<"hi">>, 1}}),
try_coerce(Output, {"Right", 2, 3}, {variant, [2, 2], 1, {2, 3}}).
nested_coerce_test() ->
Contract = "
contract C =
type pair('t) = 't * 't
record r = { f1 : pair(int), f2: pair(string) }
entrypoint f(): r = { f1 = (1, 2), f2 = (\"a\", \"b\") }
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
try_coerce(Output,
#{ "f1" => {1, 2}, "f2" => {"a", "b"}},
{tuple, {{tuple, {1, 2}}, {tuple, {<<"a">>, <<"b">>}}}}).
state_coerce_test() ->
Contract = "
contract C =
type state = int
entrypoint init(): state = 0
",
{ok, AACI} = aaci_from_string(Contract),
{ok, {[], Output}} = aaci_lookup_spec(AACI, "init"),
try_coerce(Output, 0, 0).

View File

@ -2,16 +2,16 @@
{type,app}.
{modules,[]}.
{prefix,"hz"}.
{desc,"Gajumaru interoperation library"}.
{author,"Craig Everett"}.
{desc,"Gajumaru interoperation library"}.
{package_id,{"otpr","hakuzaru",{0,2,0}}}.
{deps,[{"otpr","erl_base58",{0,1,0}},
{deps,[{"otpr","gmbytecode",{3,4,1}},
{"otpr","gmserialization",{0,1,2}},
{"otpr","base58",{0,1,1}},
{"otpr","eblake2",{1,0,1}},
{"otpr","ec_utils",{1,0,0}},
{"otpr","aebytecode",{3,2,1}},
{"otpr","aesophia",{7,1,2}},
{"otpr","aeserialization",{0,1,0}},
{"otpr","zj",{1,1,0}},
{"otpr","eblake2",{1,0,0}},
{"otpr","getopt",{1,0,2}}]}.
{key_name,none}.
{a_email,"ceverett@tsuriai.jp"}.
@ -20,6 +20,5 @@
{file_exts,[]}.
{license,"MIT"}.
{repo_url,"https://gitlab.com/ioecs/hakuzaru"}.
{tags,["aeternity","qpq","gajumaru","blockchain","hakuzaru","crypto","ae",
"defi"]}.
{tags,["qpq","gajumaru","blockchain","hakuzaru","crypto","defi"]}.
{ws_url,"https://gitlab.com/ioecs/hakuzaru"}.