diff --git a/src/hz.erl b/src/hz.erl index 277f122..c36dbc3 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -716,7 +716,7 @@ decode_bytearray_fate(EncodedStr) -> decode_bytearray(Type, EncodedStr) -> case decode_bytearray_fate(EncodedStr) of {ok, none} -> {ok, none}; - {ok, Object} -> hz_aaci:coerce(Type, Object, from_fate); + {ok, Object} -> hz_aaci:fate_to_erlang(Type, Object); {error, Reason} -> {error, Reason} end. @@ -1518,7 +1518,7 @@ encode_call_data({aaci, Label}, Fun, Args) -> end. encode_call_data2(ArgDef, Fun, Args) -> - case hz_aaci:coerce_bindings(ArgDef, Args, to_fate) of + case hz_aaci:erlang_args_to_fate(ArgDef, Args) of {ok, Coerced} -> gmb_fate_abi:create_calldata(Fun, Coerced); Errors -> Errors end. diff --git a/src/hz_aaci.erl b/src/hz_aaci.erl index 0d56ef7..2d21846 100644 --- a/src/hz_aaci.erl +++ b/src/hz_aaci.erl @@ -18,8 +18,9 @@ % Contract call and serialization interface functions -export([prepare_contract/1, prepare_aaci/1, - coerce/3, - coerce_bindings/3, + erlang_to_fate/2, + fate_to_erlang/2, + erlang_args_to_fate/2, aaci_get_function_signature/2]). %%% Types @@ -441,97 +442,51 @@ substitute_opaque_types(Bindings, Types) -> Each = fun(Type) -> substitute_opaque_type(Bindings, Type) end, lists:map(Each, Types). -%%% Coerce +%%% Erlang to FATE -coerce_bindings(VarTypes, Terms, Direction) -> +erlang_args_to_fate(VarTypes, Terms) -> DefLength = length(VarTypes), ArgLength = length(Terms), if - DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), Direction, arg); + DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), to_fate, arg); DefLength > ArgLength -> {error, too_few_args}; DefLength < ArgLength -> {error, too_many_args} end. -coerce_zipped_bindings(Bindings, Direction, Tag) -> - coerce_zipped_bindings(Bindings, Direction, Tag, [], []). - -coerce_zipped_bindings([Next | Rest], Direction, Tag, Good, Broken) -> - {{ArgName, Type}, Term} = Next, - case coerce(Type, Term, Direction) of - {ok, NewTerm} -> - coerce_zipped_bindings(Rest, Direction, Tag, [NewTerm | Good], Broken); - {error, Errors} -> - Wrapped = wrap_errors({Tag, ArgName}, Errors), - coerce_zipped_bindings(Rest, Direction, Tag, Good, [Wrapped | Broken]) - end; -coerce_zipped_bindings([], _, _, Good, []) -> - {ok, lists:reverse(Good)}; -coerce_zipped_bindings([], _, _, _, Broken) -> - {error, combine_errors(Broken)}. - -wrap_errors(Location, Errors) -> - F = fun({Error, Path}) -> - {Error, [Location | Path]} - end, - lists:map(F, Errors). - -combine_errors(Broken) -> - F = fun(NextErrors, Acc) -> - NextErrors ++ Acc - end, - lists:foldl(F, [], Broken). - -coerce({_, _, integer}, S, _) when is_integer(S) -> +erlang_to_fate({_, _, integer}, S) when is_integer(S) -> {ok, S}; -coerce({O, N, integer}, S, to_fate) when is_list(S) -> +erlang_to_fate({O, N, integer}, S) when is_list(S) -> try Val = list_to_integer(S), {ok, Val} catch error:badarg -> single_error({invalid, O, N, S}) end; -coerce({O, N, address}, S, to_fate) -> +erlang_to_fate({O, N, address}, S) -> 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) -> +erlang_to_fate({O, N, contract}, S) -> 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) -> +erlang_to_fate({_, _, signature}, S) 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) -> +erlang_to_fate({O, N, signature}, S) -> 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) -> +%erlang_to_fate({_, _, channel}, S) when is_binary(S) -> %{ok, {channel, S}}; -%coerce({_, _, channel}, {channel, S}, from_fate) when is_binary(S) -> - %{ok, S}; -coerce({_, _, boolean}, true, _) -> +erlang_to_fate({_, _, boolean}, true) -> {ok, true}; -coerce({_, _, boolean}, "true", _) -> +erlang_to_fate({_, _, boolean}, "true") -> {ok, true}; -coerce({_, _, boolean}, false, _) -> +erlang_to_fate({_, _, boolean}, false) -> {ok, false}; -coerce({_, _, boolean}, "false", _) -> +erlang_to_fate({_, _, boolean}, "false") -> {ok, false}; -coerce({O, N, boolean}, S, _) -> - single_error({invalid, O, N, S}); -coerce({O, N, string}, Str, Direction) -> - Result = case Direction of - to_fate -> unicode:characters_to_binary(Str); - from_fate -> unicode:characters_to_list(Str) - end, - case Result of +erlang_to_fate({O, N, string}, Str) -> + case unicode:characters_to_binary(Str) of {error, _, _} -> single_error({invalid, O, N, Str}); {incomplete, _, _} -> @@ -539,9 +494,9 @@ coerce({O, N, string}, Str, Direction) -> StrBin -> {ok, StrBin} end; -coerce({_, _, char}, Val, _Direction) when is_integer(Val) -> +erlang_to_fate({_, _, char}, Val) when is_integer(Val) -> {ok, Val}; -coerce({O, N, char}, Str, to_fate) -> +erlang_to_fate({O, N, char}, Str) -> Result = unicode:characters_to_list(Str), case Result of {error, _, _} -> @@ -553,27 +508,24 @@ coerce({O, N, char}, Str, to_fate) -> _ -> single_error({invalid, O, N, Str}) end; -coerce({O, N, {bytes, [Count]}}, Bytes, _Direction) when is_bitstring(Bytes) -> +erlang_to_fate({O, N, {bytes, [Count]}}, Bytes) 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) -> +erlang_to_fate({_, _, bits}, Num) when is_integer(Num) -> {ok, {bits, Num}}; -coerce({_, _, bits}, Bits, to_fate) when is_bitstring(Bits) -> +erlang_to_fate({_, _, bits}, Bits) 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) -> - coerce_map(KeyType, ValType, Data, Direction); -coerce({O, N, {tuple, ElementTypes}}, Data, to_fate) when is_tuple(Data) -> +erlang_to_fate({_, _, {list, [Type]}}, Data) when is_list(Data) -> + coerce_list(Type, Data, to_fate); +erlang_to_fate({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) -> + coerce_map(KeyType, ValType, Data, to_fate); +erlang_to_fate({O, N, {tuple, ElementTypes}}, Data) when is_tuple(Data) -> ElementList = tuple_to_list(Data), coerce_tuple(O, N, ElementTypes, ElementList, to_fate); -coerce({O, N, {tuple, ElementTypes}}, {tuple, Data}, from_fate) -> - ElementList = tuple_to_list(Data), - coerce_tuple(O, N, ElementTypes, ElementList, from_fate); -coerce({O, N, {variant, Variants}}, Data, to_fate) when is_tuple(Data), tuple_size(Data) > 0 -> +erlang_to_fate({O, N, {variant, Variants}}, Name) when is_list(Name) -> + erlang_to_fate({O, N, {variant, Variants}}, {Name}); +erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_size(Data) > 0 -> [Name | Terms] = tuple_to_list(Data), case lookup_variant(Name, Variants) of {Tag, TermTypes} -> @@ -582,17 +534,9 @@ coerce({O, N, {variant, Variants}}, Data, to_fate) when is_tuple(Data), tuple_si ValidNames = [Valid || {Valid, _} <- Variants], single_error({invalid_variant, O, N, Name, ValidNames}) end; -coerce({O, N, {variant, Variants}}, Name, to_fate) when is_list(Name) -> - coerce({O, N, {variant, Variants}}, {Name}, to_fate); -coerce({O, N, {variant, Variants}}, {variant, _, Tag, Tuple}, from_fate) -> - Terms = tuple_to_list(Tuple), - {Name, TermTypes} = lists:nth(Tag + 1, Variants), - coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, from_fate); -coerce({O, N, {record, MemberTypes}}, Map, to_fate) when is_map(Map) -> +erlang_to_fate({O, N, {record, MemberTypes}}, Map) when is_map(Map) -> coerce_map_to_record(O, N, MemberTypes, Map); -coerce({O, N, {record, MemberTypes}}, {tuple, Tuple}, from_fate) -> - coerce_record_to_map(O, N, MemberTypes, Tuple); -coerce({O, N, {unknown_type, _}}, Data, _) -> +erlang_to_fate({O, N, {unknown_type, _}}, Data) -> case N of already_normalized -> Message = "Warning: Unknown type ~p. Using term ~p as is.~n", @@ -602,24 +546,7 @@ coerce({O, N, {unknown_type, _}}, Data, _) -> io:format(Message, [O, N, Data]) end, {ok, Data}; -coerce({O, N, _}, Data, from_fate) -> - case N of - already_normalized -> - io:format("Warning: Unimplemented type ~p.~nUsing term as is:~n~p~n", [O, Data]); - _ -> - io:format("Warning: Unimplemented type ~p (i.e. ~p).~nUsing term as is:~n~p~n", [O, N, Data]) - end, - {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}. +erlang_to_fate({O, N, _}, Data) -> single_error({invalid, O, N, Data}). coerce_chain_object(_, _, _, _, {raw, Binary}) -> {ok, Binary}; @@ -644,12 +571,38 @@ decode_chain_object(Tag, S) -> error:incorrect_size -> {error, incorrect_size} end. +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_zipped_bindings(Bindings, Direction, Tag) -> + coerce_zipped_bindings(Bindings, Direction, Tag, [], []). + +coerce_zipped_bindings([Next | Rest], Direction, Tag, Good, Broken) -> + {{ArgName, Type}, Term} = Next, + case coerce_direction(Type, Term, Direction) of + {ok, NewTerm} -> + coerce_zipped_bindings(Rest, Direction, Tag, [NewTerm | Good], Broken); + {error, Errors} -> + Wrapped = wrap_errors({Tag, ArgName}, Errors), + coerce_zipped_bindings(Rest, Direction, Tag, Good, [Wrapped | Broken]) + end; +coerce_zipped_bindings([], _, _, Good, []) -> + {ok, lists:reverse(Good)}; +coerce_zipped_bindings([], _, _, _, Broken) -> + {error, combine_errors(Broken)}. + coerce_list(Type, Elements, Direction) -> % 0 index since it represents a sophia list coerce_list(Type, Elements, Direction, 0, [], []). coerce_list(Type, [Next | Rest], Direction, Index, Good, Broken) -> - case coerce(Type, Next, Direction) of + case coerce_direction(Type, Next, Direction) of {ok, Coerced} -> coerce_list(Type, Rest, Direction, Index + 1, [Coerced | Good], Broken); {error, Errors} -> Wrapped = wrap_errors({index, Index}, Errors), @@ -672,7 +625,7 @@ coerce_map(KeyType, ValType, Remaining, Direction, Good, Broken) -> end. coerce_map2(KeyType, ValType, Remaining, Direction, Good, Broken, K, V) -> - case coerce(KeyType, K, Direction) of + case coerce_direction(KeyType, K, Direction) of {ok, KFATE} -> coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE); {error, Errors} -> @@ -683,7 +636,7 @@ coerce_map2(KeyType, ValType, Remaining, Direction, Good, Broken, K, V) -> end. coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE) -> - case coerce(ValType, V, Direction) of + case coerce_direction(ValType, V, Direction) of {ok, VFATE} -> NewGood = Good#{KFATE => VFATE}, coerce_map(KeyType, ValType, Remaining, Direction, NewGood, Broken); @@ -720,11 +673,6 @@ coerce_tuple(O, N, TermTypes, Terms, Direction) -> Errors -> Errors end. -% Wraps a single error in a list, along with an empty path, so that other -% accumulating error handlers can work with it. -single_error(Reason) -> - {error, [{Reason, []}]}. - coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, Direction) -> % FIXME: we could go through and add the variant tag to the adt_element % paths? @@ -751,7 +699,7 @@ coerce_tuple_elements(Types, Terms, Direction, Tag) -> coerce_tuple_elements(Types, Terms, Direction, Tag, 0, [], []). coerce_tuple_elements([Type | Types], [Term | Terms], Direction, Tag, Index, Good, Broken) -> - case coerce(Type, Term, Direction) of + case coerce_direction(Type, Term, Direction) of {ok, Value} -> coerce_tuple_elements(Types, Terms, Direction, Tag, Index + 1, [Value | Good], Broken); {error, Errors} -> @@ -820,6 +768,91 @@ zip_record_field({Name, Type}, {Remaining, Missing}) -> {missing, {Remaining, [Name | Missing]}} end. +% Wraps a single error in a list, along with an empty path, so that other +% accumulating error handlers can work with it. +single_error(Reason) -> + {error, [{Reason, []}]}. + +wrap_errors(Location, Errors) -> + F = fun({Error, Path}) -> + {Error, [Location | Path]} + end, + lists:map(F, Errors). + +combine_errors(Broken) -> + F = fun(NextErrors, Acc) -> + NextErrors ++ Acc + end, + lists:foldl(F, [], Broken). + + +%%% FATE to Erlang + +% Not sure if this is needed... fate_to_erlang shouldn't fail. +coerce_direction(Type, Term, to_fate) -> + erlang_to_fate(Type, Term); +coerce_direction(Type, Term, from_fate) -> + fate_to_erlang(Type, Term). + +fate_to_erlang({_, _, integer}, S) when is_integer(S) -> + {ok, S}; +fate_to_erlang({_, _, address}, {address, Bin}) -> + Address = gmser_api_encoder:encode(account_pubkey, Bin), + {ok, unicode:characters_to_list(Address)}; +fate_to_erlang({_, _, contract}, {contract, Bin}) -> + Address = gmser_api_encoder:encode(contract_pubkey, Bin), + {ok, unicode:characters_to_list(Address)}; +fate_to_erlang({_, _, signature}, Bin) -> + Address = gmser_api_encoder:encode(signature, Bin), + {ok, unicode:characters_to_list(Address)}; +%fate_to_erlang({_, _, channel}, {channel, S}) when is_binary(S) -> + %{ok, S}; +fate_to_erlang({_, _, boolean}, true) -> + {ok, true}; +fate_to_erlang({_, _, boolean}, false) -> + {ok, false}; +fate_to_erlang({_, _, string}, Bin) -> + Str = binary_to_list(Bin), + {ok, Str}; +fate_to_erlang({_, _, char}, Val) -> + {ok, Val}; +fate_to_erlang({O, N, {bytes, [Count]}}, Bytes) when is_bitstring(Bytes) -> + coerce_bytes(O, N, Count, Bytes); +fate_to_erlang({_, _, bits}, {bits, Num}) -> + {ok, Num}; +fate_to_erlang({_, _, {list, [Type]}}, Data) when is_list(Data) -> + coerce_list(Type, Data, from_fate); +fate_to_erlang({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) -> + coerce_map(KeyType, ValType, Data, from_fate); +fate_to_erlang({O, N, {tuple, ElementTypes}}, {tuple, Data}) -> + ElementList = tuple_to_list(Data), + coerce_tuple(O, N, ElementTypes, ElementList, from_fate); +fate_to_erlang({O, N, {variant, Variants}}, {variant, _, Tag, Tuple}) -> + Terms = tuple_to_list(Tuple), + {Name, TermTypes} = lists:nth(Tag + 1, Variants), + coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, from_fate); +fate_to_erlang({O, N, {record, MemberTypes}}, {tuple, Tuple}) -> + coerce_record_to_map(O, N, MemberTypes, Tuple); +fate_to_erlang({O, N, {unknown_type, _}}, Data) -> + case N of + already_normalized -> + Message = "Warning: Unknown type ~p. Using term ~p as is.~n", + io:format(Message, [O, Data]); + _ -> + Message = "Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n", + io:format(Message, [O, N, Data]) + end, + {ok, Data}; +fate_to_erlang({O, N, _}, Data) -> + case N of + already_normalized -> + io:format("Warning: Unimplemented type ~p.~nUsing term as is:~n~p~n", [O, Data]); + _ -> + io:format("Warning: Unimplemented type ~p (i.e. ~p).~nUsing term as is:~n~p~n", [O, N, Data]) + end, + {ok, Data}. + + %%% AACI Getters @@ -841,38 +874,44 @@ aaci_get_function_signature({aaci, _, FunDefs, _}, Fun) -> end. -%%% Simple coerce/3 tests +%%% Simple FATE/erlang tests -% Round trip coerce run for the eunit tests below. If these results don't match -% then the test should fail. -try_coerce(Type, Sophia, Fate) -> - % Run both first, to see if they fail to produce any result. - {ok, FateActual} = coerce(Type, Sophia, to_fate), - {ok, SophiaActual} = coerce(Type, Fate, from_fate), - % Now check that the results were what we expected. +check_erlang_to_fate(Type, Sophia, Fate) -> + {ok, FateActual} = erlang_to_fate(Type, Sophia), case FateActual of Fate -> ok; _ -> erlang:error({to_fate_failed, Fate, FateActual}) - end, + end. + +check_fate_to_erlang(Type, Fate, Sophia) -> + {ok, SophiaActual} = fate_to_erlang(Type, Fate), + % Now check that the results were what we expected. case SophiaActual of Sophia -> ok; _ -> erlang:error({from_fate_failed, Sophia, SophiaActual}) - end, + end. + +% Round trip coerce run for the eunit tests below. If these results don't match +% then the test should fail. +check_roundtrip(Type, Sophia, Fate) -> + check_erlang_to_fate(Type, Sophia, Fate), + check_fate_to_erlang(Type, Fate, Sophia), + % Finally, check that the FATE result is something that gmb understands. gmb_fate_encoding:serialize(Fate), ok. coerce_int_test() -> {ok, Type} = annotate_type(integer, #{}), - try_coerce(Type, 123, 123). + check_roundtrip(Type, 123, 123). coerce_address_test() -> {ok, Type} = annotate_type(address, #{}), - try_coerce(Type, + check_roundtrip(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, @@ -880,7 +919,7 @@ coerce_address_test() -> coerce_contract_test() -> {ok, Type} = annotate_type(contract, #{}), - try_coerce(Type, + check_roundtrip(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, @@ -888,7 +927,7 @@ coerce_contract_test() -> coerce_signature_test() -> {ok, Type} = annotate_type(signature, #{}), - try_coerce(Type, + check_roundtrip(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, @@ -901,69 +940,69 @@ coerce_signature_binary_test() -> 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, Binary} = erlang_to_fate(Type, {raw, Binary}), + {ok, Binary} = erlang_to_fate(Type, Binary), ok. coerce_bool_test() -> {ok, Type} = annotate_type(boolean, #{}), - try_coerce(Type, true, true), - try_coerce(Type, false, false). + check_roundtrip(Type, true, true), + check_roundtrip(Type, false, false). coerce_string_test() -> {ok, Type} = annotate_type(string, #{}), - try_coerce(Type, "hello world", <<"hello world">>). + check_roundtrip(Type, "hello world", <<"hello world">>). coerce_list_test() -> {ok, Type} = annotate_type({list, [string]}, #{}), - try_coerce(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]). + check_roundtrip(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]). coerce_map_test() -> {ok, Type} = annotate_type({map, [string, {list, [integer]}]}, #{}), - try_coerce(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}). + check_roundtrip(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}). coerce_tuple_test() -> {ok, Type} = annotate_type({tuple, [integer, string]}, #{}), - try_coerce(Type, {123, "456"}, {tuple, {123, <<"456">>}}). + check_roundtrip(Type, {123, "456"}, {tuple, {123, <<"456">>}}). coerce_variant_test() -> {ok, Type} = annotate_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}}). + check_roundtrip(Type, {"A", 123}, {variant, [1, 2], 0, {123}}), + check_roundtrip(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}}). + check_roundtrip(Type, {"None"}, {variant, [0, 1], 0, {}}), + check_roundtrip(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}}). + check_roundtrip(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">>}}). + check_roundtrip(Type, {<<"abcd">>, <<"efghi">>}, {tuple, {<<"abcd">>, <<"efghi">>}}). coerce_bits_test() -> {ok, Type} = annotate_type(bits, #{}), - try_coerce(Type, 5, {bits, 5}). + check_roundtrip(Type, 5, {bits, 5}). coerce_char_test() -> {ok, Type} = annotate_type(char, #{}), - try_coerce(Type, $?, $?). + check_roundtrip(Type, $?, $?). coerce_unicode_test() -> {ok, Type} = annotate_type(char, #{}), % Latin Small Letter C with cedilla and acute - {ok, $ḉ} = coerce(Type, <<"ḉ"/utf8>>, to_fate), + {ok, $ḉ} = erlang_to_fate(Type, <<"ḉ"/utf8>>), ok. coerce_hash_test() -> {ok, Type} = annotate_type("hash", builtin_typedefs()), Hash = list_to_binary(lists:seq(1,32)), - try_coerce(Type, Hash, Hash), + check_roundtrip(Type, Hash, Hash), ok. @@ -985,7 +1024,7 @@ namespace_coerce_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(AACI, "f"), - try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). + check_roundtrip(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). record_substitution_test() -> Contract = " @@ -995,7 +1034,7 @@ record_substitution_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(AACI, "f"), - try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). + check_roundtrip(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). tuple_substitution_test() -> Contract = " @@ -1005,7 +1044,7 @@ tuple_substitution_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(AACI, "f"), - try_coerce(Output, {1, 2, "hello"}, {tuple, {1, 2, <<"hello">>}}). + check_roundtrip(Output, {1, 2, "hello"}, {tuple, {1, 2, <<"hello">>}}). variant_substitution_test() -> Contract = " @@ -1015,8 +1054,8 @@ variant_substitution_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(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}}). + check_roundtrip(Output, {"Left", "hi", 1}, {variant, [2, 2], 0, {<<"hi">>, 1}}), + check_roundtrip(Output, {"Right", 2, 3}, {variant, [2, 2], 1, {2, 3}}). nested_coerce_test() -> Contract = " @@ -1027,7 +1066,7 @@ nested_coerce_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(AACI, "f"), - try_coerce(Output, + check_roundtrip(Output, #{ "f1" => {1, 2}, "f2" => {"a", "b"}}, {tuple, {{tuple, {1, 2}}, {tuple, {<<"a">>, <<"b">>}}}}). @@ -1039,7 +1078,7 @@ state_coerce_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[], Output}} = aaci_get_function_signature(AACI, "init"), - try_coerce(Output, 0, 0). + check_roundtrip(Output, 0, 0). param_test() -> Contract = " @@ -1049,8 +1088,8 @@ param_test() -> ", {ok, AACI} = aaci_from_string(Contract), {ok, {[{"x", Input}], Output}} = aaci_get_function_signature(AACI, "init"), - try_coerce(Input, 0, 0), - try_coerce(Output, 0, 0). + check_roundtrip(Input, 0, 0), + check_roundtrip(Output, 0, 0). %%% Obscure Sophia types where we should check the AACI as well @@ -1126,21 +1165,21 @@ name_coerce_test() -> {ok, TTL} = annotate_type("Chain.ttl", builtin_typedefs()), TTLSoph = {"FixedTTL", 0}, TTLFate = {variant, [1, 1], 0, {0}}, - try_coerce(TTL, TTLSoph, TTLFate), + check_roundtrip(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), + check_roundtrip(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). + check_roundtrip(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, {}}), + check_roundtrip(NonOption, {"None"}, {variant, [0, 1], 0, {}}), {ok, NonList} = annotate_type({list, ["void"]}, builtin_typedefs()), - try_coerce(NonList, [], []). + check_roundtrip(NonList, [], []).