diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index 685be51..0d71ca7 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,7 +3,7 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.6.1"}, + {vsn,"0.6.2"}, {modules,[hakuzaru,hz,hz_fetcher,hz_grids,hz_key_master,hz_man, hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index 379c0b5..5f89478 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.6.1"). +-vsn("0.6.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index ffe0168..9ea9454 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.6.1"). +-vsn("0.6.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). @@ -890,7 +890,7 @@ contract_create(CreatorID, Path, InitArgs) -> when CreatorID :: pubkey(), Nonce :: pos_integer(), Amount :: non_neg_integer(), - TTL :: pos_integer(), + TTL :: non_neg_integer(), Gas :: pos_integer(), GasPrice :: pos_integer(), Path :: file:filename(), @@ -1226,7 +1226,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> Gas :: pos_integer(), GasPrice :: pos_integer(), Amount :: non_neg_integer(), - TTL :: pos_integer(), + TTL :: non_neg_integer(), AACI :: aaci(), ConID :: unicode:chardata(), Fun :: string(), @@ -1419,7 +1419,8 @@ prepare_aaci(ACI) -> % down to the concrete types they actually represent. We annotate each % subexpression of this concrete type with other info too, in case it helps % make error messages easier to understand. - Specs = annotate_function_specs(OpaqueSpecs, TypeDefs, #{}), + InternalTypeDefs = maps:merge(builtin_typedefs(), TypeDefs), + Specs = annotate_function_specs(OpaqueSpecs, InternalTypeDefs, #{}), {aaci, Name, Specs, TypeDefs}. @@ -1520,20 +1521,85 @@ opaque_type(Params, #{variant := VariantDefs}) -> {variant, Variants}; opaque_type(Params, #{tuple := TypeDefs}) -> {tuple, [opaque_type(Params, Type) || Type <- TypeDefs]}; +opaque_type(_, #{bytes := Count}) -> + {bytes, [Count]}; opaque_type(Params, Pair) when is_map(Pair) -> [{Name, TypeArgs}] = maps:to_list(Pair), {opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}. -% atoms for builtins, strings (lists) for user-defined types -opaque_type_name(<<"int">>) -> integer; -opaque_type_name(<<"address">>) -> address; -opaque_type_name(<<"contract">>) -> contract; -opaque_type_name(<<"bool">>) -> boolean; -opaque_type_name(<<"option">>) -> option; -opaque_type_name(<<"list">>) -> list; -opaque_type_name(<<"map">>) -> map; -opaque_type_name(<<"string">>) -> string; -opaque_type_name(Name) -> binary_to_list(Name). +% Atoms for any builtins that aren't qualified by a namespace in Sophia. +% Everything else stays as a string, user-defined or not. +opaque_type_name(<<"int">>) -> integer; +opaque_type_name(<<"bool">>) -> boolean; +opaque_type_name(<<"bits">>) -> bits; +opaque_type_name(<<"char">>) -> char; +opaque_type_name(<<"string">>) -> string; +opaque_type_name(<<"address">>) -> address; +opaque_type_name(<<"signature">>) -> signature; +opaque_type_name(<<"contract">>) -> contract; +opaque_type_name(<<"list">>) -> list; +opaque_type_name(<<"map">>) -> map; +% I'm not sure how to produce channels in Sophia source, but they seem to exist +% in gmb still. +opaque_type_name(<<"channel">>) -> channel; +opaque_type_name(Name) -> binary_to_list(Name). + +builtin_typedefs() -> + #{"unit" => {[], {tuple, []}}, + "void" => {[], {variant, []}}, + "hash" => {[], {bytes, [32]}}, + "option" => {["'T"], {variant, [{"None", []}, + {"Some", [{var, "'T"}]}]}}, + "Chain.ttl" => {[], {variant, [{"FixedTTL", [integer]}, + {"RelativeTTL", [integer]}]}}, + "AENS.pointee" => {[], {variant, [{"AccountPt", [address]}, + {"OraclePt", [address]}, + {"ContractPt", [address]}, + {"ChannelPt", [address]}]}}, + "AENS.name" => {[], {variant, [{"Name", [address, + "Chain.ttl", + {map, [string, "AENS.pointee"]}]}]}}, + "AENSv2.pointee" => {[], {variant, [{"AccountPt", [address]}, + {"OraclePt", [address]}, + {"ContractPt", [address]}, + {"ChannelPt", [address]}, + {"DataPt", [{bytes, [any]}]}]}}, + "AENSv2.name" => {[], {variant, [{"Name", [address, + "Chain.ttl", + {map, [string, "AENSv2.pointee"]}]}]}}, + "Chain.ga_meta_tx" => {[], {variant, [{"GAMetaTx", [address, integer]}]}}, + "Chain.paying_for_tx" => {[], {variant, [{"PayingForTx", [address, integer]}]}}, + "Chain.base_tx" => {[], {variant, [{"SpendTx", [address, integer, string]}, + {"OracleRegisterTx", []}, + {"OracleQueryTx", []}, + {"OracleResponseTx", []}, + {"OracleExtendTx", []}, + {"NamePreclaimTx", []}, + {"NameClaimTx", ["hash"]}, + {"NameUpdateTx", [string]}, + {"NameRevokeTx", ["hash"]}, + {"NameTransferTx", [address, string]}, + {"ChannelCreateTx", [address]}, + {"ChannelDepositTx", [address, integer]}, + {"ChannelWithdrawTx", [address, integer]}, + {"ChannelForceProgressTx", [address]}, + {"ChannelCloseMutualTx", [address]}, + {"ChannelCloseSoloTx", [address]}, + {"ChannelSlashTx", [address]}, + {"ChannelSettleTx", [address]}, + {"ChannelSnapshotSoloTx", [address]}, + {"ContractCreateTx", [integer]}, + {"ContractCallTx", [address, integer]}, + {"GAAttachTx", []}]}}, + "Chain.tx" => {[], {record, [{"paying_for", {"option", ["Chain.paying_for_tx"]}}, + {"ga_metas", {list, ["Chain.ga_meta_tx"]}}, + {"actor", address}, + {"fee", integer}, + {"ttl", integer}, + {"tx", "Chain.base_tx"}]}}, + "MCL_BLS12_381.fr" => {[], {bytes, [32]}}, + "MCL_BLS12_381.fp" => {[], {bytes, [48]}} + }. % Type preparation has two goals. First, we need a data structure that can be % traversed quickly, to take sophia-esque erlang expressions and turn them into @@ -1576,6 +1642,10 @@ annotate_type(T, Types) -> Error end. +annotate_type2(T, _, _, unknown_type, _) -> + % If a type is unknown, then it should not be reported as the normalized + % name. + {ok, {T, unknown_type, unknown_type}}; annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) -> case annotate_type_subexpressions(NExpanded, Types) of {ok, Flat} -> @@ -1597,6 +1667,10 @@ annotate_types([], _Types, Acc) -> annotate_type_subexpressions(PrimitiveType, _Types) when is_atom(PrimitiveType) -> {ok, PrimitiveType}; +annotate_type_subexpressions({bytes, [Count]}, _Types) -> + % bytes is weird, because it has an argument, but that argument isn't an + % opaque type. + {ok, {bytes, [Count]}}; annotate_type_subexpressions({variant, VariantsOpaque}, Types) -> case annotate_variants(VariantsOpaque, Types, []) of {ok, Variants} -> {ok, {variant, Variants}}; @@ -1629,116 +1703,99 @@ annotate_variants([{Name, Elems} | Rest], Types, Acc) -> annotate_variants([], _Types, Acc) -> {ok, lists:reverse(Acc)}. -normalize_opaque_type(T, Types) -> - case type_is_expanded(T) of - false -> normalize_opaque_type(T, Types, true); - true -> {ok, true, T, T} - end. +% This function evaluates type aliases in a loop, until eventually a usable +% definition is found. +normalize_opaque_type(T, Types) -> normalize_opaque_type(T, Types, true). % FIXME detect infinite loops % FIXME detect builtins with the wrong number of arguments % FIXME should nullary types have an empty list of arguments added before now? -normalize_opaque_type({option, [T]}, _Types, IsFirst) -> - % Just like user-made ADTs, 'option' is considered part of the type, and so - % options are considered normalised. - {ok, IsFirst, {option, [T]}, {variant, [{"None", []}, {"Some", [T]}]}}; +normalize_opaque_type(T, _Types, IsFirst) when is_atom(T) -> + % Once we have eliminated the above rewrite cases, all other cases are + % handled explicitly by the coerce logic, and so are considered normalized. + {ok, IsFirst, T, T}; +normalize_opaque_type(Type = {T, _}, _Types, IsFirst) when is_atom(T) -> + % Once we have eliminated the above rewrite cases, all other cases are + % handled explicitly by the coerce logic, and so are considered normalized. + {ok, IsFirst, Type, Type}; normalize_opaque_type(T, Types, IsFirst) when is_list(T) -> + % Lists/strings indicate userspace types, which may require arg + % substitutions. Convert to an explicit but empty arg list, for uniformity. normalize_opaque_type({T, []}, Types, IsFirst); normalize_opaque_type({T, TypeArgs}, Types, IsFirst) when is_list(T) -> case maps:find(T, Types) of - %{error, invalid_aci}; % FIXME more info error -> - {ok, IsFirst, {T, TypeArgs}, {unknown_type, TypeArgs}}; + % We couldn't find this named type... Keep building the AACI, but + % mark this type expression as unknown, so that FATE coercions + % aren't attempted. + {ok, IsFirst, {T, TypeArgs}, unknown_type}; {ok, {TypeParamNames, Definition}} -> - Bindings = lists:zip(TypeParamNames, TypeArgs), - normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition) + % We have a definition for this type, including names for whatever + % args we have been given. Subtitute our args into this. + NewType = substitute_opaque_type(TypeParamNames, Definition, TypeArgs), + % Now continue on to see if we need to restart the loop or not. + normalize_opaque_type2(IsFirst, {T, TypeArgs}, NewType, Types) end. -normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition) -> - SubResult = - case Bindings of - [] -> {ok, Definition}; - _ -> substitute_opaque_type(Bindings, Definition) - end, - case SubResult of - % Type names were already normalized if they were ADTs or records, - % since for those connectives the name is considered part of the type. - {ok, NextT = {variant, _}} -> - {ok, IsFirst, {T, TypeArgs}, NextT}; - {ok, NextT = {record, _}} -> - {ok, IsFirst, {T, TypeArgs}, NextT}; - % Everything else has to be substituted down to a built-in connective - % to be considered normalized. - {ok, NextT} -> - normalize_opaque_type3(NextT, Types); - Error -> - Error - end. +normalize_opaque_type2(IsFirst, PrevType, NextType = {variant, _}, _) -> + % We have reduced to a variant. Report the type name as the normalized + % type, but also provide the variant definition itself as the candidate + % flattened type for further annotation. + {ok, IsFirst, PrevType, NextType}; +normalize_opaque_type2(IsFirst, PrevType, NextType = {record, _}, _) -> + % We have reduced to a record. Report the type name as the normalized + % type, but also provide the record definition itself as the candidate + % flattened type for further annotation. + {ok, IsFirst, PrevType, NextType}; +normalize_opaque_type2(_, _, NextType, Types) -> + % Not a variant or record yet, so go back to the start of the loop. + % It will no longer be the first iteration. + normalize_opaque_type(NextType, Types, false). -% while this does look like normalize_opaque_type/2, it sets IsFirst to false -% instead of true, and is part of the loop, instead of being an initial -% condition for the loop. -normalize_opaque_type3(NextT, Types) -> - case type_is_expanded(NextT) of - false -> normalize_opaque_type(NextT, Types, false); - true -> {ok, false, NextT, NextT} - end. +% Perform a beta-reduction on a type expression. +substitute_opaque_type([], Definition, _) -> + % There are no parameters to substitute. This is the simplest way of + % defining type aliases, records, and variants, so we should make sure to + % short circuit all the recursive descent logic, since it won't actually + % do anything. + Definition; +substitute_opaque_type(TypeParamNames, Definition, TypeArgs) -> + % Bundle the param names alongside the args that we want to substitute, so + % that we can keyfind the one list. + Bindings = lists:zip(TypeParamNames, TypeArgs), + substitute_opaque_type(Bindings, Definition). -% Strings indicate names that should be substituted. Atoms indicate built in -% types, which don't need to be expanded, except for option. -type_is_expanded({option, _}) -> false; -type_is_expanded(X) when is_atom(X) -> true; -type_is_expanded({X, _}) when is_atom(X) -> true; -type_is_expanded(_) -> false. - -% Skip traversal if there is nothing to substitute. This will often be the -% most common case. substitute_opaque_type(Bindings, {var, VarName}) -> case lists:keyfind(VarName, 1, Bindings) of - 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 + {_, TypeArg} -> TypeArg; + % No valid ACI will create this case. Regardless, the user should + % still be able to specify arbitrary gmb FATE terms for whatever this + % is meant to be. + false -> unknown_type end; +substitute_opaque_type(Bindings, {variant, Variants}) -> + Each = fun({VariantName, Elements}) -> + NewElements = substitute_opaque_types(Bindings, Elements), + {VariantName, NewElements} + end, + NewVariants = lists:map(Each, Variants), + {variant, NewVariants}; +substitute_opaque_type(Bindings, {record, Fields}) -> + Each = fun({FieldName, FieldType}) -> + NewType = substitute_opaque_type(Bindings, FieldType), + {FieldName, NewType} + end, + NewFields = lists:map(Each, Fields), + {record, NewFields}; substitute_opaque_type(Bindings, {Connective, Args}) -> - case substitute_opaque_types(Bindings, Args, []) of - {ok, Result} -> {ok, {Connective, Result}}; - Error -> Error - end; + NewArgs = substitute_opaque_types(Bindings, Args), + {Connective, NewArgs}; substitute_opaque_type(_Bindings, Type) -> - {ok, Type}. + 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 - {ok, Result} -> substitute_opaque_types(Bindings, Rest, [Result | Acc]); - Error -> Error - end; -substitute_opaque_types(_Bindings, [], Acc) -> - {ok, lists:reverse(Acc)}. +substitute_opaque_types(Bindings, Types) -> + Each = fun(Type) -> substitute_opaque_type(Bindings, Type) end, + lists:map(Each, Types). coerce_bindings(VarTypes, Terms, Direction) -> DefLength = length(VarTypes), @@ -1788,33 +1845,39 @@ coerce({O, N, integer}, S, to_fate) when is_list(S) -> error:badarg -> single_error({invalid, O, N, S}) end; coerce({O, N, address}, S, to_fate) -> - try - case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of - {account_pubkey, Key} -> {ok, {address, Key}}; - _ -> single_error({invalid, O, N, S}) - end - catch - error:_ -> single_error({invalid, O, N, S}) - end; + coerce_chain_object(O, N, address, account_pubkey, S); coerce({_, _, address}, {address, Bin}, from_fate) -> Address = gmser_api_encoder:encode(account_pubkey, Bin), {ok, unicode:characters_to_list(Address)}; coerce({O, N, contract}, S, to_fate) -> - try - case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of - {contract_pubkey, Key} -> {ok, {contract, Key}}; - _ -> single_error({invalid, O, N, S}) - end - catch - error:_ -> single_error({invalid, O, N, S}) - end; + coerce_chain_object(O, N, contract, contract_pubkey, S); coerce({_, _, contract}, {contract, Bin}, from_fate) -> Address = gmser_api_encoder:encode(contract_pubkey, Bin), {ok, unicode:characters_to_list(Address)}; +coerce({_, _, signature}, S, to_fate) when is_binary(S) andalso (byte_size(S) =:= 64) -> + % Usually to pass a binary in, you need to wrap it as {raw, Binary}, but + % since sg_... strings OR hex blobs can be used as signatures in Sophia, we + % special case this case based on the length. Even if a binary starts with + % "sg_", 64 characters is not enough to represent a 64 byte signature, so + % the most optimistic interpretation is to use the binary directly. + {ok, S}; +coerce({O, N, signature}, S, to_fate) -> + coerce_chain_object(O, N, signature, signature, S); +coerce({_, _, signature}, Bin, from_fate) -> + Address = gmser_api_encoder:encode(signature, Bin), + {ok, unicode:characters_to_list(Address)}; +%coerce({_, _, channel}, S, to_fate) when is_binary(S) -> + %{ok, {channel, S}}; +%coerce({_, _, channel}, {channel, S}, from_fate) when is_binary(S) -> + %{ok, S}; coerce({_, _, boolean}, true, _) -> {ok, true}; +coerce({_, _, boolean}, "true", _) -> + {ok, true}; coerce({_, _, boolean}, false, _) -> {ok, false}; +coerce({_, _, boolean}, "false", _) -> + {ok, false}; coerce({O, N, boolean}, S, _) -> single_error({invalid, O, N, S}); coerce({O, N, string}, Str, Direction) -> @@ -1830,6 +1893,30 @@ coerce({O, N, string}, Str, Direction) -> StrBin -> {ok, StrBin} end; +coerce({_, _, char}, Val, _Direction) when is_integer(Val) -> + {ok, Val}; +coerce({O, N, char}, Str, to_fate) -> + Result = unicode:characters_to_list(Str), + case Result of + {error, _, _} -> + single_error({invalid, O, N, Str}); + {incomplete, _, _} -> + single_error({invalid, O, N, Str}); + [C] -> + {ok, C}; + _ -> + single_error({invalid, O, N, Str}) + end; +coerce({O, N, {bytes, [Count]}}, Bytes, _Direction) when is_bitstring(Bytes) -> + coerce_bytes(O, N, Count, Bytes); +coerce({_, _, bits}, {bits, Num}, from_fate) -> + {ok, Num}; +coerce({_, _, bits}, Num, to_fate) when is_integer(Num) -> + {ok, {bits, Num}}; +coerce({_, _, bits}, Bits, to_fate) when is_bitstring(Bits) -> + Size = bit_size(Bits), + <> = Bits, + {ok, {bits, IntValue}}; coerce({_, _, {list, [Type]}}, Data, Direction) when is_list(Data) -> coerce_list(Type, Data, Direction); coerce({_, _, {map, [KeyType, ValType]}}, Data, Direction) when is_map(Data) -> @@ -1879,6 +1966,38 @@ coerce({O, N, _}, Data, from_fate) -> {ok, Data}; coerce({O, N, _}, Data, _) -> single_error({invalid, O, N, Data}). +coerce_bytes(O, N, _, Bytes) when bit_size(Bytes) rem 8 /= 0 -> + single_error({partial_bytes, O, N, bit_size(Bytes)}); +coerce_bytes(_, _, any, Bytes) -> + {ok, Bytes}; +coerce_bytes(O, N, Count, Bytes) when byte_size(Bytes) /= Count -> + single_error({incorrect_size, O, N, Bytes}); +coerce_bytes(_, _, _, Bytes) -> + {ok, Bytes}. + +coerce_chain_object(_, _, _, _, {raw, Binary}) -> + {ok, Binary}; +coerce_chain_object(O, N, T, Tag, S) -> + case decode_chain_object(Tag, S) of + {ok, Data} -> {ok, coerce_chain_object2(T, Data)}; + {error, Reason} -> single_error({Reason, O, N, S}) + end. + +coerce_chain_object2(address, Data) -> {address, Data}; +coerce_chain_object2(contract, Data) -> {contract, Data}; +coerce_chain_object2(signature, Data) -> Data. + +decode_chain_object(Tag, S) -> + try + case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of + {Tag, Data} -> {ok, Data}; + {_, _} -> {error, wrong_prefix} + end + catch + error:missing_prefix -> {error, missing_prefix}; + error:incorrect_size -> {error, incorrect_size} + end. + coerce_list(Type, Elements, Direction) -> % 0 index since it represents a sophia list coerce_list(Type, Elements, Direction, 0, [], []). @@ -2409,6 +2528,8 @@ try_coerce(Type, Sophia, Fate) -> _ -> erlang:error({from_fate_failed, Sophia, SophiaActual}) end, + % Finally, check that the FATE result is something that gmb understands. + gmb_fate_encoding:serialize(Fate), ok. coerce_int_test() -> @@ -2431,6 +2552,25 @@ coerce_contract_test() -> 167,208,53,78,40,235,2,163,132,36,47,183,228,151,9, 210,39,214>>}). +coerce_signature_test() -> + {ok, Type} = annotate_type(signature, #{}), + try_coerce(Type, + "sg_XDyF8LJC4tpMyAySvpaG1f5V9F2XxAbRx9iuVjvvdNMwVracLhzAuXhRM5kXAFtpwW1DCHuz5jGehUayCah4jub32Ti2n", + <<231,4,97,129,16,173,37,42,194,249,28,94,134,163,208,84,22,135, + 169,85,212,142,14,12,233,252,97,50,193,158,229,51,123,206,222, + 249,2,3,85,173,106,150,243,253,89,128,248,52,195,140,95,114, + 233,110,119,143,206,137,124,36,63,154,85,7>>). + +coerce_signature_binary_test() -> + {ok, Type} = annotate_type(signature, #{}), + Binary = <<231,4,97,129,16,173,37,42,194,249,28,94,134,163,208,84,22,135, + 169,85,212,142,14,12,233,252,97,50,193,158,229,51,123,206,222, + 249,2,3,85,173,106,150,243,253,89,128,248,52,195,140,95,114, + 233,110,119,143,206,137,124,36,63,154,85,7>>, + {ok, Binary} = coerce(Type, {raw, Binary}, to_fate), + {ok, Binary} = coerce(Type, Binary, to_fate), + ok. + coerce_bool_test() -> {ok, Type} = annotate_type(boolean, #{}), try_coerce(Type, true, true), @@ -2459,10 +2599,40 @@ coerce_variant_test() -> try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}), try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}). +coerce_option_test() -> + {ok, Type} = annotate_type({"option", [integer]}, builtin_typedefs()), + try_coerce(Type, {"None"}, {variant, [0, 1], 0, {}}), + try_coerce(Type, {"Some", 1}, {variant, [0, 1], 1, {1}}). + coerce_record_test() -> {ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}), try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). +coerce_bytes_test() -> + {ok, Type} = annotate_type({tuple, [{bytes, [4]}, {bytes, [any]}]}, #{}), + try_coerce(Type, {<<"abcd">>, <<"efghi">>}, {tuple, {<<"abcd">>, <<"efghi">>}}). + +coerce_bits_test() -> + {ok, Type} = annotate_type(bits, #{}), + try_coerce(Type, 5, {bits, 5}). + +coerce_char_test() -> + {ok, Type} = annotate_type(char, #{}), + try_coerce(Type, $?, $?). + +coerce_unicode_test() -> + {ok, Type} = annotate_type(char, #{}), + % Latin Small Letter C with cedilla and acute + {ok, $ḉ} = coerce(Type, <<"ḉ"/utf8>>, to_fate), + ok. + +coerce_hash_test() -> + {ok, Type} = annotate_type("hash", builtin_typedefs()), + Hash = list_to_binary(lists:seq(1,32)), + try_coerce(Type, Hash, Hash), + ok. + + %%% Complex AACI paramter and namespace tests @@ -2549,3 +2719,95 @@ param_test() -> try_coerce(Input, 0, 0), try_coerce(Output, 0, 0). +%%% Obscure Sophia types where we should check the AACI as well + +obscure_aaci_test() -> + Contract = " + include \"Set.aes\" + contract C = + entrypoint options(): option(int) = None + entrypoint fixed_bytes(): bytes(4) = #DEADBEEF + entrypoint any_bytes(): bytes() = Bytes.to_any_size(#112233) + entrypoint bits(): bits = Bits.all + entrypoint character(): char = 'a' + entrypoint hash(): hash = #00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF + entrypoint unit(): unit = () + + entrypoint ttl(x): Chain.ttl = FixedTTL(x) + entrypoint paying_for(x, y): Chain.paying_for_tx = Chain.PayingForTx(x, y) + entrypoint ga_meta_tx(x, y): Chain.ga_meta_tx = Chain.GAMetaTx(x, y) + entrypoint base_tx(x, y, z): Chain.base_tx = Chain.SpendTx(x, y, z) + entrypoint tx(a, b, c, d, e, f): Chain.tx = + {paying_for = a, + ga_metas = b, + actor = c, + fee = d, + ttl = e, + tx = f} + + entrypoint pointee(x): AENS.pointee = AENS.AccountPt(x) + entrypoint name(x, y, z): AENS.name = AENS.Name(x, y, z) + entrypoint pointee2(x): AENSv2.pointee = AENSv2.DataPt(x) + entrypoint name2(x, y, z): AENSv2.name = AENSv2.Name(x, y, z) + + entrypoint fr(x): MCL_BLS12_381.fr = x + entrypoint fp(x): MCL_BLS12_381.fp = x + + entrypoint set(): Set.set(int) = Set.new() + + ", + {ok, AACI} = aaci_from_string(Contract), + + {ok, {[], {{bytes, [4]}, _, _}}} = aaci_lookup_spec(AACI, "fixed_bytes"), + {ok, {[], {{bytes, [any]}, _, _}}} = aaci_lookup_spec(AACI, "any_bytes"), + {ok, {[], {bits, _, _}}} = aaci_lookup_spec(AACI, "bits"), + {ok, {[], {char, _, _}}} = aaci_lookup_spec(AACI, "character"), + + {ok, {[], {{"option", [integer]}, _, {variant, [{"None", []}, {"Some", [_]}]}}}} = aaci_lookup_spec(AACI, "options"), + {ok, {[], {"hash", _, {bytes, [32]}}}} = aaci_lookup_spec(AACI, "hash"), + {ok, {[], {"unit", _, {tuple, []}}}} = aaci_lookup_spec(AACI, "unit"), + + {ok, {_, {"Chain.ttl", _, {variant, _}}}} = aaci_lookup_spec(AACI, "ttl"), + {ok, {_, {"Chain.paying_for_tx", _, {variant, _}}}} = aaci_lookup_spec(AACI, "paying_for"), + {ok, {_, {"Chain.ga_meta_tx", _, {variant, _}}}} = aaci_lookup_spec(AACI, "ga_meta_tx"), + {ok, {_, {"Chain.base_tx", _, {variant, _}}}} = aaci_lookup_spec(AACI, "base_tx"), + {ok, {_, {"Chain.tx", _, {record, _}}}} = aaci_lookup_spec(AACI, "tx"), + + {ok, {_, {"AENS.pointee", _, {variant, _}}}} = aaci_lookup_spec(AACI, "pointee"), + {ok, {_, {"AENS.name", _, {variant, _}}}} = aaci_lookup_spec(AACI, "name"), + {ok, {_, {"AENSv2.pointee", _, {variant, _}}}} = aaci_lookup_spec(AACI, "pointee2"), + {ok, {_, {"AENSv2.name", _, {variant, _}}}} = aaci_lookup_spec(AACI, "name2"), + + {ok, {_, {"MCL_BLS12_381.fr", _, {bytes, [32]}}}} = aaci_lookup_spec(AACI, "fr"), + {ok, {_, {"MCL_BLS12_381.fp", _, {bytes, [48]}}}} = aaci_lookup_spec(AACI, "fp"), + + {ok, {[], {{"Set.set", [integer]}, _, {record, [{"to_map", _}]}}}} = aaci_lookup_spec(AACI, "set"), + + ok. + +name_coerce_test() -> + AddrSoph = "ak_2FTnrGfV8qsfHpaSEHpBrziioCpwwzLqSevHqfxQY3PaAAdARx", + AddrFate = {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>>}, + {ok, TTL} = annotate_type("Chain.ttl", builtin_typedefs()), + TTLSoph = {"FixedTTL", 0}, + TTLFate = {variant, [1, 1], 0, {0}}, + try_coerce(TTL, TTLSoph, TTLFate), + {ok, Pointee} = annotate_type("AENS.pointee", builtin_typedefs()), + PointeeSoph = {"AccountPt", AddrSoph}, + PointeeFate = {variant, [1, 1, 1, 1], 0, {AddrFate}}, + try_coerce(Pointee, PointeeSoph, PointeeFate), + {ok, Name} = annotate_type("AENS.name", builtin_typedefs()), + NameSoph = {"Name", AddrSoph, TTLSoph, #{"myname" => PointeeSoph}}, + NameFate = {variant, [3], 0, {AddrFate, TTLFate, #{<<"myname">> => PointeeFate}}}, + try_coerce(Name, NameSoph, NameFate). + +void_coerce_test() -> + % Void itself can't be represented, but other types built out of void are + % valid. + {ok, NonOption} = annotate_type({"option", ["void"]}, builtin_typedefs()), + try_coerce(NonOption, {"None"}, {variant, [0, 1], 0, {}}), + {ok, NonList} = annotate_type({list, ["void"]}, builtin_typedefs()), + try_coerce(NonList, [], []). + diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index 421ef67..7f196b5 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.6.1"). +-vsn("0.6.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_grids.erl b/src/hz_grids.erl index be3ec03..4a9107f 100644 --- a/src/hz_grids.erl +++ b/src/hz_grids.erl @@ -37,7 +37,7 @@ %%% @end -module(hz_grids). --vsn("0.6.1"). +-vsn("0.6.2"). -export([url/2, parse/1, req/2, req/3]). diff --git a/src/hz_key_master.erl b/src/hz_key_master.erl index c880ee1..ceed8f1 100644 --- a/src/hz_key_master.erl +++ b/src/hz_key_master.erl @@ -8,7 +8,7 @@ %%% @end -module(hz_key_master). --vsn("0.6.1"). +-vsn("0.6.2"). -export([make_key/1, encode/1, decode/1]). diff --git a/src/hz_man.erl b/src/hz_man.erl index cb8bf23..c6f31a7 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.6.1"). +-vsn("0.6.2"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index 793cd15..187d2de 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.6.1"). +-vsn("0.6.2"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index f798ddc..f41a3b4 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,9 +2,9 @@ {type,app}. {modules,[]}. {prefix,"hz"}. -{desc,"Gajumaru interoperation library"}. {author,"Craig Everett"}. -{package_id,{"otpr","hakuzaru",{0,6,1}}}. +{desc,"Gajumaru interoperation library"}. +{package_id,{"otpr","hakuzaru",{0,6,2}}}. {deps,[{"otpr","sophia",{9,0,0}}, {"otpr","gmserialization",{0,1,3}}, {"otpr","gmbytecode",{3,4,1}},