Compare commits

...

3 Commits

Author SHA1 Message Date
Jarvis Carroll
a1be8cbc14 coerce hashes
It turns out there are a lot of types that, like option, should only be
valid as an opaque/normalized type, but should be substituted for
something different in the flat representation. If we restructure things
a little then we can implement all of these in one go.
2025-09-23 19:22:30 +10:00
Jarvis Carroll
3822bb69c9 Coerce binaries as-is
Sophia accepts both sg_... and #... as signatures, so we should probably
accept binaries as signatures directly. People might expect to be able
to put the listy string "#..." in too, but that is more complex to do.
2025-09-23 17:34:55 +10:00
Jarvis Carroll
6506fc91bd Also coerce unicode strings to FATE
This is mainly so that gajudesk can pass text box content to hz as-is,
but also allows users to pass utf8 binaries in, if they want to, for
some reason.
2025-09-23 17:07:41 +10:00

View File

@ -1658,6 +1658,12 @@ normalize_opaque_type(T, Types) ->
true -> {ok, true, T, T}
end.
% This function evaluates type aliases in a loop, until eventually a usable
% definition is found.
%
% It also evaluates built-in and standard library types such as options and
% names, to their defined variant representation, as well as evaluating
% certain binary types like hash, fp, and fr, into their byte representations.
% 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?
@ -1665,6 +1671,10 @@ 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(hash, _Types, IsFirst) ->
% For coercion purposes, hash is indistinguishable from bytes(32), so we
% treat it like a type alias.
{ok, IsFirst, hash, {bytes, [32]}};
normalize_opaque_type(T, Types, IsFirst) when is_list(T) ->
normalize_opaque_type({T, []}, Types, IsFirst);
normalize_opaque_type({T, TypeArgs}, Types, IsFirst) when is_list(T) ->
@ -1709,7 +1719,10 @@ normalize_opaque_type3(NextT, Types) ->
% Strings indicate names that should be substituted. Atoms indicate built in
% types, which don't need to be expanded, except for option.
% TODO: Stop calling this, so that we can stop redundantly enumerating all the
% built in types.
type_is_expanded({option, _}) -> false;
type_is_expanded(hash) -> false;
type_is_expanded(X) when is_atom(X) -> true;
type_is_expanded({X, _}) when is_atom(X) -> true;
type_is_expanded(_) -> false.
@ -1820,6 +1833,11 @@ coerce({O, N, contract}, S, to_fate) ->
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) ->
@ -1850,6 +1868,18 @@ coerce({O, N, string}, Str, Direction) ->
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) ->
@ -2502,6 +2532,15 @@ coerce_signature_test() ->
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),
@ -2546,6 +2585,19 @@ 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, #{}),
Hash = list_to_binary(lists:seq(1,32)),
try_coerce(Type, Hash, Hash),
ok.
%%% Complex AACI paramter and namespace tests
@ -2642,20 +2694,20 @@ obscure_aaci_test() ->
entrypoint any_bytes(): bytes() = Bytes.to_any_size(#112233)
entrypoint bits(): bits = Bits.all
entrypoint character(): char = 'a'
entrypoint hash(): hash = #00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF
",
{ok, AACI} = aaci_from_string(Contract),
IntAnnotated = {integer, already_normalized, integer},
OptionFlat = {variant, [{"None", []}, {"Some", [IntAnnotated]}]},
OptionAnnotated = {{option, [integer]}, already_normalized, OptionFlat},
{ok, {[], OptionAnnotated}} = aaci_lookup_spec(AACI, "options"),
{ok, {[], {{option, [integer]}, _, _}}} = aaci_lookup_spec(AACI, "options"),
{ok, {[], {_, _, {bytes, [4]}}}} = aaci_lookup_spec(AACI, "fixed_bytes"),
{ok, {[], {_, _, {bytes, [any]}}}} = aaci_lookup_spec(AACI, "any_bytes"),
{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, {[], {bits, _, _}}} = aaci_lookup_spec(AACI, "bits"),
{ok, {[], {_, _, char}}} = aaci_lookup_spec(AACI, "character"),
{ok, {[], {char, _, _}}} = aaci_lookup_spec(AACI, "character"),
{ok, {[], {hash, _, _}}} = aaci_lookup_spec(AACI, "hash"),
ok.