From c3788b2b5a9073a6bdd9018c991d9b94407e6551 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 30 Jun 2023 16:21:50 +0200 Subject: [PATCH] [Ceres]: Add arbitrary size byte arrays (#456) * Extend compiler to allow bytes()/bytes as type * Add split_any, to_fixed_size, size, to_any_size, Int.to_bytes and String.to_bytes * Add tests * Use and and not andalso in unify, some things have side-effects * Bump to aebytecode v3.3.0 * Changelog + update documentation * fix wording in documentation --- CHANGELOG.md | 4 + docs/sophia_stdlib.md | 60 +++++++++++-- priv/stdlib/String.aes | 3 + rebar.config | 2 +- src/aeso_aci.erl | 2 + src/aeso_ast_infer_types.erl | 140 +++++++++++++++++++++--------- src/aeso_ast_to_fcode.erl | 22 +++-- src/aeso_fcode_to_fate.erl | 15 ++++ src/aeso_parser.erl | 13 ++- src/aeso_pretty.erl | 4 +- test/aeso_compiler_tests.erl | 35 +++++++- test/contracts/bad_bytes_to_x.aes | 5 ++ test/contracts/bytes_misc.aes | 27 ++++++ test/contracts/bytes_to_x.aes | 2 + 14 files changed, 270 insertions(+), 64 deletions(-) create mode 100644 test/contracts/bad_bytes_to_x.aes create mode 100644 test/contracts/bytes_misc.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c6d454..e152cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 only contains the old datatypes, that can be used to interface existing contracts. Standard library `AENSCompat` is added to convert between old and new pointers. +- Introduce arbitrary sized binary arrays (type `bytes()`); adding `Bytes.split_any`, + `Bytes.to_fixed_size`, `Bytes.to_any_size`, `Bytes.size`, `String.to_bytes`, + and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary + sized byte arrays. ### Changed ### Removed - `Bitwise.aes` standard library is removed - the builtin operations are superior. diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index f8d10c1..feec8dd 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -371,7 +371,7 @@ Each bit is true if and only if it was 1 in `a` and 0 in `b` ### Bytes -#### to_int +#### to\_int ``` Bytes.to_int(b : bytes(n)) : int ``` @@ -379,7 +379,7 @@ Bytes.to_int(b : bytes(n)) : int Interprets the byte array as a big endian integer -#### to_str +#### to\_str ``` Bytes.to_str(b : bytes(n)) : string ``` @@ -392,7 +392,8 @@ Returns the hexadecimal representation of the byte array Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) ``` -Concatenates two byte arrays +Concatenates two byte arrays. If `m` and `n` are known at compile time, the +result can be used as a fixed size byte array, otherwise it has type `bytes()`. #### split @@ -402,6 +403,38 @@ Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) Splits a byte array at given index +#### split\_any +``` +Bytes.split_any(a : bytes(), at : int) : option(bytes() * bytes(n)) +``` + +Splits an arbitrary size byte array at index `at`. If `at` is positive split +from the beginning of the array, if `at` is negative, split `abs(at)` from the +_end_ of the array. If the array is shorter than `abs(at)` then `None` is +returned. + +#### to\_fixed\_size +``` +Bytes.to_fixed_size(a : bytes()) : option(bytes(n)) +``` + +Converts an arbitrary size byte array to a fix size byte array. If `a` is +`n` bytes, `None` is returned. + +#### to\_any\_size +``` +Bytes.to_any_size(a : bytes(n)) : bytes() +``` + +Converts a fixed size byte array to an arbitrary size byte array. This is a +no-op at run-time, and only used during type checking. + +#### size +``` +Bytes.size(a : bytes()) : int +``` + +Computes the lenght/size of a byte array. ### Call @@ -830,12 +863,20 @@ Verifies a standard 64-byte ECDSA signature (`R || S`). ### Int -#### to_str +#### to\_str ``` -Int.to_str : int => string +Int.to_str(n : int) : string ``` -Casts integer to string using decimal representation +Casts the integer to a string (in decimal representation). + +#### to\_bytes +``` +Int.to_bytes(n : int, size : int) : bytes() +``` + +Casts the integer to a byte array with `size` bytes (big endian, truncating if +necessary). ### Map @@ -2412,6 +2453,13 @@ to_int(s : string) : option(int) Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into an integer. If the string doesn't contain a valid number `None` is returned. +#### to\_bytes +``` +to_bytes(s : string) : bytes() +``` + +Converts string into byte array. + #### sha3 ``` sha3(s : string) : hash diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes index 33f813b..9927774 100644 --- a/priv/stdlib/String.aes +++ b/priv/stdlib/String.aes @@ -1,5 +1,8 @@ include "List.aes" namespace String = + // Gives a bytes() representation of the string + function to_bytes(s : string) : bytes() = StringInternal.to_bytes(s) + // Computes the SHA3/Keccak hash of the string function sha3(s : string) : hash = StringInternal.sha3(s) // Computes the SHA256 hash of the string. diff --git a/rebar.config b/rebar.config index c19c2fd..5d42b29 100644 --- a/rebar.config +++ b/rebar.config @@ -14,7 +14,7 @@ ]}. {relx, [{release, {aesophia, "8.0.0"}, - [aesophia, aebytecode, getopt]}, + [aesophia, aebytecode]}, {dev_mode, true}, {include_erts, false}, diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index d0c7c68..5cf5aff 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -282,6 +282,8 @@ decode_type(#{list := [Et]}) -> decode_type(#{map := Ets}) -> Ts = decode_types(Ets), ["map",$(,lists:join(",", Ts),$)]; +decode_type(#{bytes := any}) -> + ["bytes()"]; decode_type(#{bytes := Len}) -> ["bytes(", integer_to_list(Len), ")"]; decode_type(#{variant := Ets}) -> diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index dd50909..ff16e26 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -89,8 +89,9 @@ -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. --type byte_constraint() :: {is_bytes, utype()} - | {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}. +-type byte_constraint() :: {is_bytes, term(), utype()} + | {is_fixed_bytes, term(), utype()} + | {add_bytes, aeso_syntax:ann(), concat | split | split_any, utype(), utype(), utype()}. -type aens_resolve_constraint() :: {aens_resolve_type, utype()}. -type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}. @@ -829,6 +830,7 @@ global_env() -> [{"length", Fun1(String, Int)}, {"concat", Fun([String, String], String)}, {"to_list", Fun1(String, List(Char))}, + {"to_bytes", Fun1(String, Bytes(any))}, {"from_list", Fun1(List(Char), String)}, {"to_upper", Fun1(String, String)}, {"to_lower", Fun1(String, String)}, @@ -859,15 +861,20 @@ global_env() -> %% Bytes BytesScope = #scope { funs = MkDefs( - [{"to_int", Fun1(Bytes(any), Int)}, - {"to_str", Fun1(Bytes(any), String)}, - {"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))}, - {"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))} + [{"to_int", Fun1(Bytes('_'), Int)}, + {"to_str", Fun1(Bytes('_'), String)}, + {"to_fixed_size", Fun1(Bytes(any), Option(Bytes(fixed)))}, + {"to_any_size", Fun1(Bytes(fixed), Bytes(any))}, + {"size", Fun1(Bytes('_'), Int)}, + {"concat", FunC(bytes_concat, [Bytes('_'), Bytes('_')], Bytes('_'))}, + {"split", FunC1(bytes_split, Bytes(fixed), Pair(Bytes(fixed), Bytes(fixed)))}, + {"split_any", Fun([Bytes(any), Int], Option(Pair(Bytes(any), Bytes(any))))} ]) }, %% Conversion - IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, - {"mulmod", Fun([Int, Int, Int], Int)}]) }, + IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, + {"to_bytes", Fun([Int, Int], Bytes(any))}, + {"mulmod", Fun([Int, Int, Int], Int)}]) }, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, {"to_bytes", Fun1(Address, Bytes(32))}, @@ -1790,8 +1797,8 @@ lookup_name(Env = #env{ namespace = NS, current_function = CurFn }, As, Id, Opti Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of - {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty); - _ when Freshen -> freshen_type(As, Ty); + {type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty, [{fun_name, Id}]); + _ when Freshen -> freshen_type(As, Ty, [{fun_name, Id}]); _ -> Ty end, {set_qname(QId, Id), Ty1} @@ -2672,7 +2679,8 @@ destroy_and_report_unsolved_constraints(Env) -> (_) -> false end, OtherCs2), {BytesCs, OtherCs4} = - lists:partition(fun({is_bytes, _}) -> true; + lists:partition(fun({is_bytes, _, _}) -> true; + ({is_fixed_bytes, _, _}) -> true; ({add_bytes, _, _, _, _, _}) -> true; (_) -> false end, OtherCs3), @@ -2801,15 +2809,21 @@ solve_constraint(Env, C = #dependent_type_constraint{}) -> check_named_argument_constraint(Env, C); solve_constraint(Env, C = #named_argument_constraint{}) -> check_named_argument_constraint(Env, C); -solve_constraint(_Env, {is_bytes, _}) -> ok; -solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) -> +solve_constraint(_Env, {is_bytes, _, _}) -> ok; +solve_constraint(_Env, {is_fixed_bytes, _, _}) -> ok; +solve_constraint(Env, {add_bytes, Ann, Action, A0, B0, C0}) -> A = unfold_types_in_type(Env, dereference(A0)), B = unfold_types_in_type(Env, dereference(B0)), C = unfold_types_in_type(Env, dereference(C0)), case {A, B, C} of - {{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); - {{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); - {_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + {{bytes_t, _, M}, {bytes_t, _, N}, _} when is_integer(M), is_integer(N) -> + unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann}); + {{bytes_t, _, M}, _, {bytes_t, _, R}} when is_integer(M), is_integer(R), R >= M -> + unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann}); + {_, {bytes_t, _, N}, {bytes_t, _, R}} when is_integer(N), is_integer(R), R >= N -> + unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann}); + {{bytes_t, _, _}, {bytes_t, _, _}, _} when Action == concat -> + unify(Env, {bytes_t, Ann, any}, C, {at, Ann}); _ -> ok end; solve_constraint(_, _) -> ok. @@ -2818,18 +2832,29 @@ check_bytes_constraints(Env, Constraints) -> InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints, T <- [A, B, C], element(1, T) /= bytes_t ], + InSplitConstraint = [ T || {add_bytes, _, split, A, B, C} <- Constraints, + T <- [A, B, C], + element(1, T) /= bytes_t ], %% Skip is_bytes constraints for types that occur in add_bytes constraints %% (no need to generate error messages for both is_bytes and add_bytes). - Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint); + Skip = fun({is_bytes, _, T}) -> lists:member(T, InAddConstraint); + ({is_fixed_bytes, _, T}) -> lists:member(T, InSplitConstraint); (_) -> false end, [ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ]. -check_bytes_constraint(Env, {is_bytes, Type}) -> +check_bytes_constraint(Env, {is_bytes, Ann, Type}) -> Type1 = unfold_types_in_type(Env, instantiate(Type)), case Type1 of - {bytes_t, _, _} -> ok; + {bytes_t, _, N} when is_integer(N); N == any -> ok; _ -> - type_error({unknown_byte_length, Type}) + type_error({unknown_byte_type, Ann, Type}) + end; +check_bytes_constraint(Env, {is_fixed_bytes, Ann, Type}) -> + Type1 = unfold_types_in_type(Env, instantiate(Type)), + case Type1 of + {bytes_t, _, N} when is_integer(N) -> ok; + _ -> + type_error({unknown_byte_length, Ann, Type}) end; check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) -> A = unfold_types_in_type(Env, instantiate(A0)), @@ -3130,9 +3155,9 @@ unify1(Env, T, {uvar, A, R}, Variance, When) -> unify1(Env, {uvar, A, R}, T, Variance, When); unify1(_Env, {tvar, _, X}, {tvar, _, X}, _Variance, _When) -> true; %% Rigid type variables unify1(Env, [A|B], [C|D], [V|Variances], When) -> - unify0(Env, A, C, V, When) andalso unify0(Env, B, D, Variances, When); + unify0(Env, A, C, V, When) and unify0(Env, B, D, Variances, When); unify1(Env, [A|B], [C|D], Variance, When) -> - unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When); + unify0(Env, A, C, Variance, When) and unify0(Env, B, D, Variance, When); unify1(_Env, X, X, _Variance, _When) -> true; unify1(_Env, _A, {id, _, "void"}, Variance, _When) @@ -3177,8 +3202,8 @@ unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) - type_error({unify_varargs, When}); unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When) when length(Args1) == length(Args2) -> - unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso - unify0(Env, Args1, Args2, opposite_variance(Variance), When) andalso + unify0(Env, Named1, Named2, opposite_variance(Variance), When) and + unify0(Env, Args1, Args2, opposite_variance(Variance), When) and unify0(Env, Result1, Result2, Variance, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, Variance, When) when length(Args1) == length(Args2), Tag == id orelse Tag == qid -> @@ -3292,49 +3317,59 @@ create_freshen_tvars() -> destroy_freshen_tvars() -> ets_delete(freshen_tvars). -freshen_type(Ann, Type) -> +freshen_type(Ann, Type, Ctx) -> create_freshen_tvars(), - Type1 = freshen(Ann, Type), + Type1 = freshen(Ann, Type, Ctx), destroy_freshen_tvars(), Type1. freshen(Type) -> - freshen(aeso_syntax:get_ann(Type), Type). + freshen(aeso_syntax:get_ann(Type), Type, none). -freshen(Ann, {tvar, _, Name}) -> +freshen(Ann, {tvar, _, Name}, _Ctx) -> NewT = case ets_lookup(freshen_tvars, Name) of [] -> fresh_uvar(Ann); [{Name, T}] -> T end, ets_insert(freshen_tvars, {Name, NewT}), NewT; -freshen(Ann, {bytes_t, _, any}) -> +freshen(Ann, {bytes_t, _, '_'}, Ctx) -> X = fresh_uvar(Ann), - add_constraint({is_bytes, X}), + add_constraint({is_bytes, Ctx, X}), X; -freshen(Ann, T) when is_tuple(T) -> - list_to_tuple(freshen(Ann, tuple_to_list(T))); -freshen(Ann, [A | B]) -> - [freshen(Ann, A) | freshen(Ann, B)]; -freshen(_, X) -> +freshen(Ann, {bytes_t, _, fixed}, Ctx) -> + X = fresh_uvar(Ann), + add_constraint({is_fixed_bytes, Ctx, X}), + X; +freshen(Ann, {fun_t, FAnn, NamedArgs, Args, Result}, Ctx) when is_list(Args) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), + [ freshen(Ann, Arg, [{arg, Ix} | Ctx]) || {Arg, Ix} <- lists:zip(Args, lists:seq(1, length(Args))) ], + freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, {fun_t, FAnn, NamedArgs, Arg, Result}, Ctx) -> + {fun_t, FAnn, freshen(Ann, NamedArgs, Ctx), freshen(Ann, Arg, Ctx), freshen(Ann, Result, [result | Ctx])}; +freshen(Ann, T, Ctx) when is_tuple(T) -> + list_to_tuple(freshen(Ann, tuple_to_list(T), Ctx)); +freshen(Ann, [A | B], Ctx) -> + [freshen(Ann, A, Ctx) | freshen(Ann, B, Ctx)]; +freshen(_, X, _Ctx) -> X. -freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) -> - FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), +freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}, Ctx) -> + FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig), Ctx), apply_typesig_constraint(Ann, Constr, FunT), FunT. apply_typesig_constraint(_Ann, none, _FunT) -> ok; apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) -> add_constraint([#is_contract_constraint{ contract_t = Type, - context = {address_to_contract, Ann}}]); + context = {address_to_contract, Ann}}]); apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> add_constraint({add_bytes, Ann, split, A, B, C}); apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) -> add_constraint([#is_contract_constraint{ contract_t = Con, - context = {bytecode_hash, Ann} }]). + context = {bytecode_hash, Ann} }]). %% Dereferences all uvars and replaces the uninstantiated ones with a @@ -3855,8 +3890,11 @@ mk_error({bad_top_level_decl, Decl}) -> Msg = io_lib:format("The definition of '~s' must appear inside a ~s.", [pp_expr(Id), What]), mk_t_err(pos(Decl), Msg); -mk_error({unknown_byte_length, Type}) -> - Msg = io_lib:format("Cannot resolve length of byte array.", []), +mk_error({unknown_byte_type, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve type of byte array in\n ~s", [pp_context(Ctx)]), + mk_t_err(pos(Type), Msg); +mk_error({unknown_byte_length, Ctx, Type}) -> + Msg = io_lib:format("Cannot resolve length of byte array in\n ~s", [pp_context(Ctx)]), mk_t_err(pos(Type), Msg); mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) -> Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n" @@ -3870,6 +3908,12 @@ mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) -> [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), pp_type(" - ", B), pp_loc(B)]), mk_t_err(pos(Ann), Msg); +mk_error({unsolved_bytes_constraint, Ann, split_any, A, B, C}) -> + Msg = io_lib:format("Failed to resolve byte arrays in call to Bytes.split_any with argument of type\n" + "~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)", + [ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A), + pp_type(" - ", B), pp_loc(B)]), + mk_t_err(pos(Ann), Msg); mk_error({failed_to_get_compiler_version, Err}) -> Msg = io_lib:format("Failed to get compiler version. Error: ~p", [Err]), mk_t_err(pos(0, 0), Msg); @@ -4236,6 +4280,18 @@ pp_type(Type) -> pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated])), 80, 80). + +pp_context([{fun_name, Id}]) -> ["a call to ", pp(Id)]; +pp_context([result | Ctx]) -> ["the result of ", pp_context(Ctx)]; +pp_context([{arg, N} | Ctx]) -> + Cnt = fun(1) -> "first"; + (2) -> "second"; + (3) -> "third"; + (I) -> io_lib:format("~pth", [I]) + end, + ["the ", Cnt(N), " argument of ", pp_context(Ctx)]; +pp_context(none) -> "unknown context". + src_file(T) -> aeso_syntax:get_ann(file, T, no_file). include_type(T) -> aeso_syntax:get_ann(include_type, T, none). line_number(T) -> aeso_syntax:get_ann(line, T, 0). @@ -4287,7 +4343,7 @@ pp({tuple_t, _, []}) -> "unit"; pp({tuple_t, _, Cpts}) -> ["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"]; -pp({bytes_t, _, any}) -> "bytes(_)"; +pp({bytes_t, _, any}) -> "bytes()"; pp({bytes_t, _, Len}) -> ["bytes(", integer_to_list(Len), ")"]; pp({app_t, _, T, []}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index a6858c2..41e754f 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -291,14 +291,15 @@ builtins() -> {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, - {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, + {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, {"to_bytes", 1}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]}, {["Char"], [{"to_int", 1}, {"from_int", 1}]}, {["Auth"], [{"tx_hash", none}, {"tx", none}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, - {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, - {["Int"], [{"to_str", 1}, {"mulmod", 2}]}, + {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}, {"to_fixed_size", 1}, + {"to_any_size", 1}, {"size", 1}, {"split_any", 2}]}, + {["Int"], [{"to_str", 1}, {"to_bytes", 2}, {"mulmod", 2}]}, {["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], @@ -611,6 +612,9 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) -> {builtin_u, FAnn, B = bytes_split, Ar} -> {fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type, {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; + {builtin_u, FAnn, B = bytes_to_fixed_size, Ar} -> + {fun_t, _, _, _, {app_t, _, {id, _, "option"}, [{bytes_t, _, N}]}} = Type, + {builtin_u, FAnn, B, Ar, [{lit, FAnn, {int, N}}]}; Other -> Other end; @@ -1166,13 +1170,13 @@ stmts_to_fcode(Env, [Expr | Stmts]) -> op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, - stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, + stringinternal_to_bytes, stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, - bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, - bits_difference, int_to_str, int_mulmod, address_to_str, address_to_bytes, crypto_verify_sig, - address_to_contract, - crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_poseidon, - crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, + bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_difference, + int_to_str, int_to_bytes, int_mulmod, + address_to_str, address_to_bytes, address_to_contract, + crypto_verify_sig, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 5034c50..b538962 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -539,6 +539,14 @@ builtin_to_scode(Env, bytes_concat, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args); builtin_to_scode(Env, bytes_split, [_, _] = Args) -> call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_split_any, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_split_any(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_fixed_size, [_, _] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_to_fixed_size(?a, ?a, ?a), Args); +builtin_to_scode(Env, bytes_to_any_size, [A]) -> + [to_scode(Env, A)]; %% no_op! +builtin_to_scode(Env, bytes_size, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_size(?a, ?a), Args); builtin_to_scode(Env, abort, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:abort(?a), Args); builtin_to_scode(Env, exit, [_] = Args) -> @@ -683,6 +691,7 @@ op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a); op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a); op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a); op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_to_bytes) -> aeb_fate_ops:str_to_bytes(?a, ?a); op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a); op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a); op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a); @@ -699,6 +708,7 @@ op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); op_to_scode(address_to_bytes) -> aeb_fate_ops:addr_to_bytes(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); +op_to_scode(int_to_bytes) -> aeb_fate_ops:int_to_bytes(?a, ?a, ?a); op_to_scode(int_mulmod) -> aeb_fate_ops:mulmod(?a, ?a, ?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a); @@ -1019,9 +1029,11 @@ attributes(I) -> {'APPEND', A, B, C} -> Pure(A, [B, C]); {'STR_JOIN', A, B, C} -> Pure(A, [B, C]); {'INT_TO_STR', A, B} -> Pure(A, B); + {'INT_TO_BYTES', A, B, C} -> Pure(A, [B, C]); {'ADDR_TO_STR', A, B} -> Pure(A, B); {'STR_REVERSE', A, B} -> Pure(A, B); {'STR_LENGTH', A, B} -> Pure(A, B); + {'STR_TO_BYTES', A, B} -> Pure(A, B); {'INT_TO_ADDR', A, B} -> Pure(A, B); {'VARIANT', A, B, C, D} -> Pure(A, [?a, B, C, D]); {'VARIANT_TEST', A, B, C} -> Pure(A, [B, C]); @@ -1055,6 +1067,9 @@ attributes(I) -> {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SPLIT_ANY', A, B, C} -> Pure(A, [B, C]); + {'BYTES_SIZE', A, B} -> Pure(A, B); + {'BYTES_TO_FIXED_SIZE', A, B, C} -> Pure(A, [B, C]); {'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]); {'IS_ORACLE', A, B} -> Pure(A, [B]); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index c89f1bd..1d9a0ac 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -264,10 +264,11 @@ type300() -> type400() -> choice( [?RULE(typeAtom(), optional(type_args()), - case _2 of - none -> _1; - {ok, Args} -> {app_t, get_ann(_1), _1, Args} - end), + any_bytes( + case _2 of + none -> _1; + {ok, Args} -> {app_t, get_ann(_1), _1, Args} + end)), ?RULE(id("bytes"), parens(token(int)), {bytes_t, get_ann(_1), element(3, _2)}) ]). @@ -792,3 +793,7 @@ auto_imports(L) when is_list(L) -> auto_imports(T) when is_tuple(T) -> auto_imports(tuple_to_list(T)); auto_imports(_) -> []. + +any_bytes({id, Ann, "bytes"}) -> {bytes_t, Ann, any}; +any_bytes({app_t, _, {id, Ann, "bytes"}, []}) -> {bytes_t, Ann, any}; +any_bytes(Type) -> Type. diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 3ef3300..2697288 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -275,7 +275,9 @@ type({tuple_t, _, Args}) -> tuple_type(Args); type({args_t, _, Args}) -> args_type(Args); -type({bytes_t, _, any}) -> text("bytes(_)"); +type({bytes_t, _, any}) -> text("bytes()"); +type({bytes_t, _, '_'}) -> text("bytes(_)"); +type({bytes_t, _, fixed}) -> text("bytes(_)"); type({bytes_t, _, Len}) -> text(lists:concat(["bytes(", Len, ")"])); type({if_t, _, Id, Then, Else}) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 5d2c417..8c0b1da 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -170,6 +170,7 @@ compilable_contracts() -> "namespace_bug", "bytes_to_x", "bytes_concat", + "bytes_misc", "aens", "aens_update", "tuple_match", @@ -448,6 +449,10 @@ failing_contracts() -> [<>, + <>]) , ?TYPE_ERROR(not_toplevel_include, [< [<>]) + , ?TYPE_ERROR(bad_bytes_to_x, + [<>, + < option('a)`\n" + "to arguments\n" + " `b : bytes(4)`">>, + <>, + <>]) , ?TYPE_ERROR(bad_bytes_concat, [< "and result type\n" " - 'c (at line 16, column 39)">>, <>]) + "Cannot resolve type of byte array in\n" + " the first argument of a call to Bytes.to_str">>]) , ?TYPE_ERROR(bad_bytes_split, [< < Cat) => dt_inv(Cat)`\nto arguments\n `f_c_to_a : (Cat) => Animal`">>, + <>, <>, @@ -977,6 +1001,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1020,6 +1047,9 @@ failing_contracts() -> <>, + <>, <>, @@ -1044,6 +1074,9 @@ failing_contracts() -> <>, + <>, <>, diff --git a/test/contracts/bad_bytes_to_x.aes b/test/contracts/bad_bytes_to_x.aes new file mode 100644 index 0000000..4e3db65 --- /dev/null +++ b/test/contracts/bad_bytes_to_x.aes @@ -0,0 +1,5 @@ +// include "String.aes" +contract BytesToX = + entrypoint fail1(b : bytes()) = Bytes.to_fixed_size(b) + entrypoint fail2(b : bytes(4)) = Bytes.to_fixed_size(b) + entrypoint fail3(b : bytes()) = Bytes.to_any_size(b) diff --git a/test/contracts/bytes_misc.aes b/test/contracts/bytes_misc.aes new file mode 100644 index 0000000..13e7fb4 --- /dev/null +++ b/test/contracts/bytes_misc.aes @@ -0,0 +1,27 @@ +include "String.aes" +contract BytesMisc = + entrypoint sizeFixed(b : bytes(4)) : int = Bytes.size(b) + entrypoint sizeAny(b : bytes()) : int = Bytes.size(b) + entrypoint int_to_bytes(i : int) : bytes() = Int.to_bytes(i, 16) + + entrypoint test(b3 : bytes(3), b7 : bytes(7), bX : bytes, i : int, s : string) = + let bi = Int.to_bytes(i, 8) + let bs = String.to_bytes(s) + + let b10 = Bytes.concat(b3, b7) + + let (b4, b6 : bytes(6)) = Bytes.split(b10) + + let Some((b8, b2)) = Bytes.split_any(bX, 8) + + let bX7 = Bytes.concat(bX, b7) + + let Some((b5, bX2)) = Bytes.split_any(bX7, 5) + + let Some((b7b, b0)) = Bytes.split_any(bX, Bytes.size(b7)) + + let Some(b5b : bytes(5)) = Bytes.to_fixed_size(b5) + + let (b1 : bytes(1), _) = Bytes.split(b5b) + + [bi, bs, b0, Bytes.to_any_size(b1), b2, Bytes.to_any_size(b4), Bytes.to_any_size(b6), b7b, b8, bX2] diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes index 6ab4852..14e150d 100644 --- a/test/contracts/bytes_to_x.aes +++ b/test/contracts/bytes_to_x.aes @@ -6,3 +6,5 @@ contract BytesToX = String.concat(Bytes.to_str(b), Bytes.to_str(#ffff)) entrypoint to_str_big(b : bytes(65)) : string = Bytes.to_str(b) + entrypoint to_fixed(b : bytes()) : option(bytes(4)) = Bytes.to_fixed_size(b) + entrypoint to_any(b : bytes(4)) = Bytes.to_any_size(b)