Complete AACI definition #6
491
src/hz.erl
491
src/hz.erl
@ -1419,7 +1419,8 @@ prepare_aaci(ACI) ->
|
|||||||
% down to the concrete types they actually represent. We annotate each
|
% down to the concrete types they actually represent. We annotate each
|
||||||
% subexpression of this concrete type with other info too, in case it helps
|
% subexpression of this concrete type with other info too, in case it helps
|
||||||
% make error messages easier to understand.
|
% 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}.
|
{aaci, Name, Specs, TypeDefs}.
|
||||||
|
|
||||||
@ -1520,20 +1521,85 @@ opaque_type(Params, #{variant := VariantDefs}) ->
|
|||||||
{variant, Variants};
|
{variant, Variants};
|
||||||
opaque_type(Params, #{tuple := TypeDefs}) ->
|
opaque_type(Params, #{tuple := TypeDefs}) ->
|
||||||
{tuple, [opaque_type(Params, Type) || Type <- TypeDefs]};
|
{tuple, [opaque_type(Params, Type) || Type <- TypeDefs]};
|
||||||
|
opaque_type(_, #{bytes := Count}) ->
|
||||||
|
{bytes, [Count]};
|
||||||
opaque_type(Params, Pair) when is_map(Pair) ->
|
opaque_type(Params, Pair) when is_map(Pair) ->
|
||||||
[{Name, TypeArgs}] = maps:to_list(Pair),
|
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||||
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||||
|
|
||||||
% atoms for builtins, strings (lists) for user-defined types
|
% Atoms for any builtins that aren't qualified by a namespace in Sophia.
|
||||||
opaque_type_name(<<"int">>) -> integer;
|
% Everything else stays as a string, user-defined or not.
|
||||||
opaque_type_name(<<"address">>) -> address;
|
opaque_type_name(<<"int">>) -> integer;
|
||||||
opaque_type_name(<<"contract">>) -> contract;
|
opaque_type_name(<<"bool">>) -> boolean;
|
||||||
opaque_type_name(<<"bool">>) -> boolean;
|
opaque_type_name(<<"bits">>) -> bits;
|
||||||
opaque_type_name(<<"option">>) -> option;
|
opaque_type_name(<<"char">>) -> char;
|
||||||
opaque_type_name(<<"list">>) -> list;
|
opaque_type_name(<<"string">>) -> string;
|
||||||
opaque_type_name(<<"map">>) -> map;
|
opaque_type_name(<<"address">>) -> address;
|
||||||
opaque_type_name(<<"string">>) -> string;
|
opaque_type_name(<<"signature">>) -> signature;
|
||||||
opaque_type_name(Name) -> binary_to_list(Name).
|
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
|
% 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
|
% traversed quickly, to take sophia-esque erlang expressions and turn them into
|
||||||
@ -1576,6 +1642,10 @@ annotate_type(T, Types) ->
|
|||||||
Error
|
Error
|
||||||
end.
|
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) ->
|
annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
||||||
case annotate_type_subexpressions(NExpanded, Types) of
|
case annotate_type_subexpressions(NExpanded, Types) of
|
||||||
{ok, Flat} ->
|
{ok, Flat} ->
|
||||||
@ -1597,6 +1667,10 @@ annotate_types([], _Types, Acc) ->
|
|||||||
|
|
||||||
annotate_type_subexpressions(PrimitiveType, _Types) when is_atom(PrimitiveType) ->
|
annotate_type_subexpressions(PrimitiveType, _Types) when is_atom(PrimitiveType) ->
|
||||||
{ok, 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) ->
|
annotate_type_subexpressions({variant, VariantsOpaque}, Types) ->
|
||||||
case annotate_variants(VariantsOpaque, Types, []) of
|
case annotate_variants(VariantsOpaque, Types, []) of
|
||||||
{ok, Variants} -> {ok, {variant, Variants}};
|
{ok, Variants} -> {ok, {variant, Variants}};
|
||||||
@ -1629,116 +1703,99 @@ annotate_variants([{Name, Elems} | Rest], Types, Acc) ->
|
|||||||
annotate_variants([], _Types, Acc) ->
|
annotate_variants([], _Types, Acc) ->
|
||||||
{ok, lists:reverse(Acc)}.
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
normalize_opaque_type(T, Types) ->
|
% This function evaluates type aliases in a loop, until eventually a usable
|
||||||
case type_is_expanded(T) of
|
% definition is found.
|
||||||
false -> normalize_opaque_type(T, Types, true);
|
normalize_opaque_type(T, Types) -> normalize_opaque_type(T, Types, true).
|
||||||
true -> {ok, true, T, T}
|
|
||||||
end.
|
|
||||||
|
|
||||||
% FIXME detect infinite loops
|
% FIXME detect infinite loops
|
||||||
% FIXME detect builtins with the wrong number of arguments
|
% FIXME detect builtins with the wrong number of arguments
|
||||||
% FIXME should nullary types have an empty list of arguments added before now?
|
% FIXME should nullary types have an empty list of arguments added before now?
|
||||||
normalize_opaque_type({option, [T]}, _Types, IsFirst) ->
|
normalize_opaque_type(T, _Types, IsFirst) when is_atom(T) ->
|
||||||
% Just like user-made ADTs, 'option' is considered part of the type, and so
|
% Once we have eliminated the above rewrite cases, all other cases are
|
||||||
% options are considered normalised.
|
% handled explicitly by the coerce logic, and so are considered normalized.
|
||||||
{ok, IsFirst, {option, [T]}, {variant, [{"None", []}, {"Some", [T]}]}};
|
{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) ->
|
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, []}, Types, IsFirst);
|
||||||
normalize_opaque_type({T, TypeArgs}, Types, IsFirst) when is_list(T) ->
|
normalize_opaque_type({T, TypeArgs}, Types, IsFirst) when is_list(T) ->
|
||||||
case maps:find(T, Types) of
|
case maps:find(T, Types) of
|
||||||
%{error, invalid_aci}; % FIXME more info
|
|
||||||
error ->
|
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}} ->
|
{ok, {TypeParamNames, Definition}} ->
|
||||||
Bindings = lists:zip(TypeParamNames, TypeArgs),
|
% We have a definition for this type, including names for whatever
|
||||||
normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition)
|
% 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.
|
end.
|
||||||
|
|
||||||
normalize_opaque_type2(T, TypeArgs, Types, IsFirst, Bindings, Definition) ->
|
normalize_opaque_type2(IsFirst, PrevType, NextType = {variant, _}, _) ->
|
||||||
SubResult =
|
% We have reduced to a variant. Report the type name as the normalized
|
||||||
case Bindings of
|
% type, but also provide the variant definition itself as the candidate
|
||||||
[] -> {ok, Definition};
|
% flattened type for further annotation.
|
||||||
_ -> substitute_opaque_type(Bindings, Definition)
|
{ok, IsFirst, PrevType, NextType};
|
||||||
end,
|
normalize_opaque_type2(IsFirst, PrevType, NextType = {record, _}, _) ->
|
||||||
case SubResult of
|
% We have reduced to a record. Report the type name as the normalized
|
||||||
% Type names were already normalized if they were ADTs or records,
|
% type, but also provide the record definition itself as the candidate
|
||||||
% since for those connectives the name is considered part of the type.
|
% flattened type for further annotation.
|
||||||
{ok, NextT = {variant, _}} ->
|
{ok, IsFirst, PrevType, NextType};
|
||||||
{ok, IsFirst, {T, TypeArgs}, NextT};
|
normalize_opaque_type2(_, _, NextType, Types) ->
|
||||||
{ok, NextT = {record, _}} ->
|
% Not a variant or record yet, so go back to the start of the loop.
|
||||||
{ok, IsFirst, {T, TypeArgs}, NextT};
|
% It will no longer be the first iteration.
|
||||||
% Everything else has to be substituted down to a built-in connective
|
normalize_opaque_type(NextType, Types, false).
|
||||||
% to be considered normalized.
|
|
||||||
{ok, NextT} ->
|
|
||||||
normalize_opaque_type3(NextT, Types);
|
|
||||||
Error ->
|
|
||||||
Error
|
|
||||||
end.
|
|
||||||
|
|
||||||
% while this does look like normalize_opaque_type/2, it sets IsFirst to false
|
% Perform a beta-reduction on a type expression.
|
||||||
% instead of true, and is part of the loop, instead of being an initial
|
substitute_opaque_type([], Definition, _) ->
|
||||||
% condition for the loop.
|
% There are no parameters to substitute. This is the simplest way of
|
||||||
normalize_opaque_type3(NextT, Types) ->
|
% defining type aliases, records, and variants, so we should make sure to
|
||||||
case type_is_expanded(NextT) of
|
% short circuit all the recursive descent logic, since it won't actually
|
||||||
false -> normalize_opaque_type(NextT, Types, false);
|
% do anything.
|
||||||
true -> {ok, false, NextT, NextT}
|
Definition;
|
||||||
end.
|
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}) ->
|
substitute_opaque_type(Bindings, {var, VarName}) ->
|
||||||
case lists:keyfind(VarName, 1, Bindings) of
|
case lists:keyfind(VarName, 1, Bindings) of
|
||||||
false -> {error, invalid_aci};
|
{_, TypeArg} -> TypeArg;
|
||||||
{_, TypeArg} -> {ok, TypeArg}
|
% No valid ACI will create this case. Regardless, the user should
|
||||||
end;
|
% still be able to specify arbitrary gmb FATE terms for whatever this
|
||||||
substitute_opaque_type(Bindings, {variant, Args}) ->
|
% is meant to be.
|
||||||
case substitute_variant_types(Bindings, Args, []) of
|
false -> unknown_type
|
||||||
{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;
|
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}) ->
|
substitute_opaque_type(Bindings, {Connective, Args}) ->
|
||||||
case substitute_opaque_types(Bindings, Args, []) of
|
NewArgs = substitute_opaque_types(Bindings, Args),
|
||||||
{ok, Result} -> {ok, {Connective, Result}};
|
{Connective, NewArgs};
|
||||||
Error -> Error
|
|
||||||
end;
|
|
||||||
substitute_opaque_type(_Bindings, Type) ->
|
substitute_opaque_type(_Bindings, Type) ->
|
||||||
{ok, Type}.
|
Type.
|
||||||
|
|
||||||
substitute_variant_types(Bindings, [{VariantName, Elements} | Rest], Acc) ->
|
substitute_opaque_types(Bindings, Types) ->
|
||||||
case substitute_opaque_types(Bindings, Elements, []) of
|
Each = fun(Type) -> substitute_opaque_type(Bindings, Type) end,
|
||||||
{ok, Result} -> substitute_variant_types(Bindings, Rest, [{VariantName, Result} | Acc]);
|
lists:map(Each, Types).
|
||||||
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)}.
|
|
||||||
|
|
||||||
coerce_bindings(VarTypes, Terms, Direction) ->
|
coerce_bindings(VarTypes, Terms, Direction) ->
|
||||||
DefLength = length(VarTypes),
|
DefLength = length(VarTypes),
|
||||||
@ -1788,33 +1845,37 @@ coerce({O, N, integer}, S, to_fate) when is_list(S) ->
|
|||||||
error:badarg -> single_error({invalid, O, N, S})
|
error:badarg -> single_error({invalid, O, N, S})
|
||||||
end;
|
end;
|
||||||
coerce({O, N, address}, S, to_fate) ->
|
coerce({O, N, address}, S, to_fate) ->
|
||||||
try
|
coerce_chain_object(O, N, address, account_pubkey, S);
|
||||||
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({_, _, address}, {address, Bin}, from_fate) ->
|
coerce({_, _, address}, {address, Bin}, from_fate) ->
|
||||||
Address = gmser_api_encoder:encode(account_pubkey, Bin),
|
Address = gmser_api_encoder:encode(account_pubkey, Bin),
|
||||||
{ok, unicode:characters_to_list(Address)};
|
{ok, unicode:characters_to_list(Address)};
|
||||||
coerce({O, N, contract}, S, to_fate) ->
|
coerce({O, N, contract}, S, to_fate) ->
|
||||||
try
|
coerce_chain_object(O, N, contract, contract_pubkey, S);
|
||||||
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({_, _, contract}, {contract, Bin}, from_fate) ->
|
coerce({_, _, contract}, {contract, Bin}, from_fate) ->
|
||||||
Address = gmser_api_encoder:encode(contract_pubkey, Bin),
|
Address = gmser_api_encoder:encode(contract_pubkey, Bin),
|
||||||
{ok, unicode:characters_to_list(Address)};
|
{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, _) ->
|
coerce({_, _, boolean}, true, _) ->
|
||||||
{ok, true};
|
{ok, true};
|
||||||
|
coerce({_, _, boolean}, "true", _) ->
|
||||||
|
{ok, true};
|
||||||
coerce({_, _, boolean}, false, _) ->
|
coerce({_, _, boolean}, false, _) ->
|
||||||
{ok, false};
|
{ok, false};
|
||||||
|
coerce({_, _, boolean}, "false", _) ->
|
||||||
|
{ok, false};
|
||||||
coerce({O, N, boolean}, S, _) ->
|
coerce({O, N, boolean}, S, _) ->
|
||||||
single_error({invalid, O, N, S});
|
single_error({invalid, O, N, S});
|
||||||
coerce({O, N, string}, Str, Direction) ->
|
coerce({O, N, string}, Str, Direction) ->
|
||||||
@ -1830,6 +1891,30 @@ coerce({O, N, string}, Str, Direction) ->
|
|||||||
StrBin ->
|
StrBin ->
|
||||||
{ok, StrBin}
|
{ok, StrBin}
|
||||||
end;
|
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),
|
||||||
|
<<IntValue:Size>> = Bits,
|
||||||
|
{ok, {bits, IntValue}};
|
||||||
coerce({_, _, {list, [Type]}}, Data, Direction) when is_list(Data) ->
|
coerce({_, _, {list, [Type]}}, Data, Direction) when is_list(Data) ->
|
||||||
coerce_list(Type, Data, Direction);
|
coerce_list(Type, Data, Direction);
|
||||||
coerce({_, _, {map, [KeyType, ValType]}}, Data, Direction) when is_map(Data) ->
|
coerce({_, _, {map, [KeyType, ValType]}}, Data, Direction) when is_map(Data) ->
|
||||||
@ -1879,6 +1964,36 @@ coerce({O, N, _}, Data, from_fate) ->
|
|||||||
{ok, Data};
|
{ok, Data};
|
||||||
coerce({O, N, _}, Data, _) -> single_error({invalid, O, N, 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) ->
|
coerce_list(Type, Elements, Direction) ->
|
||||||
% 0 index since it represents a sophia list
|
% 0 index since it represents a sophia list
|
||||||
coerce_list(Type, Elements, Direction, 0, [], []).
|
coerce_list(Type, Elements, Direction, 0, [], []).
|
||||||
@ -2409,6 +2524,8 @@ try_coerce(Type, Sophia, Fate) ->
|
|||||||
_ ->
|
_ ->
|
||||||
erlang:error({from_fate_failed, Sophia, SophiaActual})
|
erlang:error({from_fate_failed, Sophia, SophiaActual})
|
||||||
end,
|
end,
|
||||||
|
% Finally, check that the FATE result is something that gmb understands.
|
||||||
|
gmb_fate_encoding:serialize(Fate),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
coerce_int_test() ->
|
coerce_int_test() ->
|
||||||
@ -2431,6 +2548,24 @@ coerce_contract_test() ->
|
|||||||
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
|
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
|
||||||
210,39,214>>}).
|
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() ->
|
coerce_bool_test() ->
|
||||||
{ok, Type} = annotate_type(boolean, #{}),
|
{ok, Type} = annotate_type(boolean, #{}),
|
||||||
try_coerce(Type, true, true),
|
try_coerce(Type, true, true),
|
||||||
@ -2459,10 +2594,40 @@ coerce_variant_test() ->
|
|||||||
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
||||||
try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}).
|
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() ->
|
coerce_record_test() ->
|
||||||
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
||||||
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
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
|
%%% Complex AACI paramter and namespace tests
|
||||||
|
|
||||||
@ -2549,3 +2714,95 @@ param_test() ->
|
|||||||
try_coerce(Input, 0, 0),
|
try_coerce(Input, 0, 0),
|
||||||
try_coerce(Output, 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, [], []).
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user