diff --git a/src/hz.erl b/src/hz.erl index 38ee508..d255c24 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -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,39 +1521,86 @@ 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. -% -% There are some magic built in types that may or may not also need atoms to -% represent them, and may or may not need to be handled explicitly in -% coerce/3, if we can't flatten them directly -% -% These types represent some FATE variant: -% Chain.ttl, AENS.pointee, AENS.name, AENSv2.pointee, AENSv2.name, -% Chain.ga_meta_tx, Chain.paying_for_tx, Chain.base_tx, -% -% And then MCL_BLS12_381.fr represent bytes(32), and MCL_BLS12_381.fp -% represents bytes(48). +% 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">>) -> integer; +opaque_type_name(<<"char">>) -> char; opaque_type_name(<<"string">>) -> string; opaque_type_name(<<"address">>) -> address; -opaque_type_name(<<"hash">>) -> hash; opaque_type_name(<<"signature">>) -> signature; -opaque_type_name(<<"bytes">>) -> bytes; opaque_type_name(<<"contract">>) -> contract; opaque_type_name(<<"list">>) -> list; opaque_type_name(<<"map">>) -> map; -opaque_type_name(<<"option">>) -> option; -opaque_type_name(<<"name">>) -> name; +% 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 % fate-esque erlang expressions that gmbytecode can serialize. Second, we need @@ -1594,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} -> @@ -1615,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}}; @@ -1647,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), @@ -1806,33 +1845,37 @@ 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) -> + % If it is a binary of 64 bytes then it can be used as is... If it is an + % sg_... string of 64 bytes, then it is too short to be valid, so just + % interpret it as a 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) -> @@ -1848,6 +1891,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) -> @@ -1897,6 +1964,36 @@ 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(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, [], []). @@ -2427,6 +2524,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() -> @@ -2449,6 +2548,24 @@ 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, Binary, to_fate), + ok. + coerce_bool_test() -> {ok, Type} = annotate_type(boolean, #{}), try_coerce(Type, true, true), @@ -2477,10 +2594,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 @@ -2567,3 +2714,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, [], []). +